From 011091016a475353b1fc4da7ec10c3da70113b9f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 17 Nov 2020 12:38:22 +0100 Subject: [PATCH 001/262] started to work on sensitivity curve processor --- Dockerfile | 1 + .../misc/SensitivityCurveProcessor.py | 476 ++++++++++++++++++ mermithid/processors/misc/__init__.py | 1 + tests/Misc_test.py | 12 + 4 files changed, 490 insertions(+) create mode 100644 mermithid/processors/misc/SensitivityCurveProcessor.py diff --git a/Dockerfile b/Dockerfile index dbde3af5..2f039dc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ RUN mkdir -p $MERMITHID_BUILD_PREFIX &&\ RUN source $COMMON_BUILD_PREFIX/setup.sh &&\ pip install iminuit &&\ + pip install numericalunits &&\ /bin/true ######################## diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py new file mode 100644 index 00000000..a6ee7886 --- /dev/null +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -0,0 +1,476 @@ +''' +Calculate sensitivity curve and plot vs. pressure +function. +Author: R. Reimann, C. Claessens +Date:11/17/2020 + +More description +''' + +from __future__ import absolute_import + + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +import configparser +import argparse +import sys +import os +from numpy import pi + +# Numericalunits is a package to handle units and some natural constants +# natural constants +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day +ppm = 1e-6 +ppb = 1e-9 + +# morpho imports +from morpho.utilities import morphologging, reader +from morpho.processors import BaseProcessor +from mermithid.misc import Constants +#from mermithid.misc import ConversionFunctions + +logger = morphologging.getLogger(__name__) + + + +__all__ = [] +__all__.append(__name__) + +class SensitivityCurveProcessor(BaseProcessor): + ''' + Description + Args: + + Inputs: + + Output: + + ''' + def InternalConfigure(self, params): + ''' + Configure + ''' + self.config_file_path = reader.read_param(params, 'config_file_path', "required") + self.comparison_config_file_path = reader.read_param(params, 'comparison_config_file_path', '') + self.add_comparison_curve = reader.read_param(params, 'add_comparison_curve', False) + self.add_track_length_axis = reader.read_param(params, 'add_track_length_axis', False) + + + + # setup sensitivities + self.sens_main = Sensitivity(self.config_file_path) + + if self.add_comparison_curve: + self.sens_ref = Sensitivity(self.comparison_config_file_path) + density_range = [1e14,1e21] + self.rhos = np.logspace(np.log10(density_range[0]), np.log10(density_range[1]), 100)/m**3 + + + return True + + def InternalRun(self): + + self.create_plot() + + if self.add_track_length_axis: + self.add_track_length_axis() + #self.add_Phase_IV(color="k") + self.add_goal(2*eV, "Phase III (%.1f eV)"%(2*eV/eV)) + sens = self.sens_main + #self.add_arrow(sens) + for a, color in self.range(1, 8): + sig = sens.BToKeErr(a*ppm*T, sens.MagneticField.nominal_field) + sens.MagneticField.usefixedvalue = True + sens.MagneticField.default_systematic_smearing = sig + sens.MagneticField.default_systematic_uncertainty = 0.05*sig + self.add_sens_line(sens, color=color) + self.add_text(5e19, 5, r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") + self.add_text(5e19, 2.3, r"$\sigma_B = 1\,\mathrm{ppm}$") + self.save("./sensitivity_phase_III.png", Veff="2cm^3") + + return True + + + def create_plot(self): + # setup axis + self.fig, self.ax = plt.subplots(figsize=(6,6)) + ax = self.ax + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) + ax.set_ylim(1e-2, 1e2) + ax.set_xlabel(r"(atomic / molecular) number density $\rho\, /\, \mathrm{m}^{-3}$") + ax.set_ylabel(r"90% CL $m_\beta$ / eV") + + def add_track_length_axis(self): + ax2 = self.ax.twiny() + ax2.set_xscale("log") + ax2.set_xlabel("(atomic) track length / s") + ax2.set_xlim(self.sens_IV.track_length(self.rhos[0])/s, + self.sens_IV.track_length(self.rhos[-1])/s) + + ax3 = self.ax.twiny() + ax3.spines["top"].set_position(("axes", 1.2)) + ax3.set_frame_on(True) + ax3.patch.set_visible(False) + for sp in ax3.spines.values(): + sp.set_visible(False) + ax3.spines["top"].set_visible(True) + ax3.set_xscale("log") + ax3.set_xlabel("(molecular) track length / s") + ax3.set_xlim(self.sens_III.track_length(self.rhos[0])/s, + self.sens_III.track_length(self.rhos[-1])/s) + self.fig.tight_layout() + + def add_Phase_IV(self, **kwargs): + limit_IV = [self.sens_IV.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.opt_IV = np.argmin(limit_IV) + self.ax.plot(self.rhos*m**3, limit_IV, **kwargs) + self.ax.axvline(self.rhos[self.opt_IV]*m**3, ls=":", color="gray", alpha=0.4) + + self.ax.axhline(0.04, color="gray", ls="--") + self.ax.text(3e14, 0.044, "Phase IV (40 meV)") + self.ax.text(1.5e14, 0.11, r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") + + def add_arrow(self, sens): + if not hasattr(self, "opt_IV"): + self.add_Phase_IV() + + def get_relative(val, axis): + xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() + return (np.log10(val)-np.log10(xmin))/(np.log10(xmax)-np.log10(xmin)) + + rho_IV = self.rhos[self.opt_IV] + track_length_IV = self.sens_IV.track_length(rho_IV) + track_length_III = self.sens_III.track_length(rho_IV) + rho_III = rho_IV*track_length_III/track_length_IV + limit_III = sens.CL90(Experiment={"number_density": rho_III}) + + x_start = get_relative(rho_IV*m**3, "x") + y_start = get_relative(2*limit_III/eV, "y") + x_stop = get_relative(rho_III*m**3, "x") + y_stop = get_relative(limit_III/eV, "y") + plt.arrow(x_start, y_start, x_stop-x_start, y_stop-y_start, + transform = self.ax.transAxes, + facecolor = 'black', + edgecolor='k', + length_includes_head=True, + head_width=0.02, + head_length=0.03, + ) + + def add_goal(self, value, label): + self.ax.axhline(value/eV, color="gray", ls="--") + self.ax.text(3e14, 1.1*value/eV, label) + + def add_sens_line(self, sens, **kwargs): + self.ax.plot(self.rhos*m**3, [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos], **kwargs) + + def add_text(self, x, y, text): + self.ax.text(x, y, text) + + def range(self, start, stop): + cmap = matplotlib.cm.get_cmap('Spectral') + norm = matplotlib.colors.Normalize(vmin=start, vmax=stop-1) + return [(idx, cmap(norm(idx))) for idx in range(start, stop)] + + def save(self, savepath, **kwargs): + self.fig.tight_layout() + keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) + metadata = {"Author": "René Reimann", + "Title": "Phase III neutrino mass sensitivity", + "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero. We use different Phase III design parameters and plot as a function of number density. As a comparison the Phase IV sensitivity is shown as well. The upper axis gives the corresponding track length for the assumed density. The arrow indicatess the Phase III point with the same track length as in the optimal point of Phase IV.", + "Keywords": keywords} + if savepath is not None: + self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=200, metadata=metadata) + self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) + + +############################################################################### + +class NameSpace(object): + def __init__(self, iteritems): + if type(iteritems) == dict: + iteritems = iteritems.items() + for k, v in iteritems: + setattr(self, k.lower(), v) + def __getattribute__(self, item): + return object.__getattribute__(self, item.lower()) + + + +############################################################################### +class Sensitivity(object): + """ + Documentation: + * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 + * Talias sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 + * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 + * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 + """ + def __init__(self, config_path): + self.cfg = configparser.ConfigParser() + with open(config_path, 'r') as configfile: + self.cfg.read_file(configfile) + + # display configuration + logger.info("Config file content:") + for sect in self.cfg.sections(): + logger.info(' Section: {}'.format(sect)) + for k,v in self.cfg.items(sect): + logger.info(' {} = {}'.format(k,v)) + + + self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) + + self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) + self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) + if self.Experiment.atomic: + self.Tritium = self.Tritium_atomic + else: + self.Tritium = self.Tritium_molecular + self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) + self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) + self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) + self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) + self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + + # SENSITIVITY + def SignalRate(self): + """signal events in the energy interval before the endpoint, scale with DeltaE**3""" + signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.Tritium.last_1ev_fraction/self.Tritium.Livetime + if not self.Experiment.atomic: + signal_rate *= 2 + return signal_rate + + def DeltaEWidth(self): + """optimal energy bin width""" + labels, sigmas, deltas = self.get_systematics() + return np.sqrt(self.Experiment.background_rate_per_eV/self.SignalRate() + + 8*np.log(2)*(np.sum(sigmas**2))) + + def StatSens(self): + """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + sig_rate = self.SignalRate() + DeltaE = self.DeltaEWidth() + sens = 4/(6*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + +self.Experiment.background_rate_per_eV*self.Experiment.LiveTime/DeltaE) + return sens + + def SystSens(self): + """Pure systematic componenet to sensitivity""" + labels, sigmas, deltas = self.get_systematics() + sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + return sens + + def sensitivity(self, **kwargs): + for sect, options in kwargs.items(): + for opt, val in options.items(): + self.__dict__[sect].__dict__[opt] = val + + StatSens = self.StatSens() + SystSens = self.SystSens() + + # Standard deviation on a measurement of m_beta**2 assuming a mass of zero + sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + return sigma_m_beta_2 + + def CL90(self, **kwargs): + return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + + # PHYSICS Functions + def frequency(self, energy, magnetic_field): + # cyclotron frequency + gamma = lambda energy: energy/(me*c0**2) + 1 # E_kin / E_0 + 1 + frequency = e*magnetic_field/(2*np.pi*me)/gamma(energy) + return frequency + + def BToKeErr(self, BErr, B): + return e*BErr/(2*np.pi*self.frequency(self.Tritium.endpoint, B)/c0**2) + + def track_length(self, rho): + Ke = self.Tritium.endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke + return 1 / (rho * self.Tritium.crosssection_te*betae*c0) + + # SYSTEMATICS + + def get_systematics(self): + # Different types of uncertainty contributions + sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() + sigma_f, delta_sigma_f = self.syst_frequency_extraction() + sigma_B, delta_sigma_B = self.syst_magnetic_field() + sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() + sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() + + labels = ["Termal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] + sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] + deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] + + if not self.Experiment.atomic: + labels.append("Molecular final state") + sigmas.append(self.Tritium.ground_state_width) + deltas.append(self.Tritium.ground_state_width_uncertainty) + + return np.array(labels), np.array(sigmas), np.array(deltas) + + def print_statistics(self): + print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + + def print_systematics(self): + labels, sigmas, deltas = self.get_systematics() + + print() + for label, sigma, delta in zip(labels, sigmas, deltas): + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") + + def syst_doppler_broadening(self): + # estimated standard deviation of Doppler broadening distribution from + # translational motion of tritium atoms / molecules + # Predicted uncertainty on standard deviation, based on expected precision + # of temperature knowledge + if self.DopplerBroadening.UseFixedValue: + sigma = self.DopplerBroadening.Default_Systematic_Smearing + delta = self.DopplerBroadening.Default_Systematic_Uncertainty + return sigma, delta + + # termal doppler broardening + gasTemp = self.DopplerBroadening.gas_temperature + mass_T = self.Tritium.mass + endpoint = self.Tritium.endpoint + + # these factors are mainly neglidible in the recoil equation below + E_rec = 3.409 * eV # maximal value # same for molecular tritium? + mbeta = 0*eV # term neglidible + betanu = 1 # neutrinos are fast + # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu + # => expectation value of cosThetanu = 0.014 + cosThetaenu = 0.014 + + Ke = endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke + Emax = endpoint + me*c0**2 + Ee = endpoint + me*c0**2 + p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) + sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) + + delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + return sigma_trans, delta_trans + + def syst_frequency_extraction(self): + # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) + # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? + # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? + + if self.FrequencyExtraction.UseFixedValue: + sigma = self.FrequencyExtraction.Default_Systematic_Smearing + delta = self.FrequencyExtraction.Default_Systematic_Uncertainty + return sigma, delta + + ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound + ts = self.FrequencyExtraction.track_timestep + Gdot = self.FrequencyExtraction.track_onset_rate + Ke = self.Tritium.endpoint + + fEndpoint = self.frequency(self.Tritium.endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke + Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power + alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope + sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level + Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) + Nsteps = 1 / (self.Experiment.number_density * self.Tritium.crosssection_te*betae*c0*ts) # Number of timesteps of length ts + + # sigma_f from Cramer-Rao lower bound in Hz + sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) + + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) + # uncertainty in alpha + delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) + # uncetainty in sigma_f in Hz due to uncertainty in alpha + delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) + + # sigma_f from Cramer-Rao lower bound in eV + sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 + delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 + + # combined sigma_f in eV + sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) + + return sigma_f, delta_sigma_f + + def syst_magnetic_field(self): + if self.MagneticField.UseFixedValue: + sigma = self.MagneticField.Default_Systematic_Smearing + delta = self.MagneticField.Default_Systematic_Uncertainty + return sigma, delta + + B = self.MagneticField.nominal_field + if self.MagneticField.useinhomogenarity: + inhomogenarity = self.MagneticField.inhomogenarity + sigma = self.BToKeErr(inhomogenarity*B, B) + return sigma, 0.05*sigma + + BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability + delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution + + BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field + delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? + + Delta_t_since_calib = self.MagneticField.time_since_calibration + shiftBdot = self.MagneticField.shift_Bdot + smearBdot = self.MagneticField.smear_Bdot + delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot + delta_smearBdot = self.MagneticField.uncertainty_smearBdot + BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) + delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) + + rRecoErr = self.MagneticField.rRecoErr + delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr + + rRecoPhiErr = self.MagneticField.rRecoPhiErr + delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr + + rProbeErr = self.MagneticField.rProbeErr + delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr + + rProbePhiErr = self.MagneticField.rProbePhiErr + delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr + + Berr = np.sqrt(BMapErr**2 + + BFlatErr**2 + + BdotErr**2 + + rRecoErr**2 + + rRecoPhiErr**2 + + rProbeErr**2 + + rProbePhiErr**2) + + delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + + BFlatErr**2 * delta_BFlatErr**2 + + BdotErr**2 * delta_BdotErr**2 + + rRecoErr**2 * delta_rRecoErr**2 + + rRecoPhiErr**2 * delta_rRecoPhiErr**2 + + rProbeErr**2 * delta_rProbeErr**2 + + rProbePhiErr**2 * delta_rProbePhiErr**2) + + return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) + + def syst_missing_tracks(self): + if self.MissingTracks.UseFixedValue: + sigma = self.MissingTracks.Default_Systematic_Smearing + delta = self.MissingTracks.Default_Systematic_Uncertainty + return sigma, delta + + def syst_plasma_effects(self): + if self.PlasmaEffects.UseFixedValue: + sigma = self.PlasmaEffects.Default_Systematic_Smearing + delta = self.PlasmaEffects.Default_Systematic_Uncertainty + return sigma, delta + else: + raise NotImplementedError() + +############################################################################### \ No newline at end of file diff --git a/mermithid/processors/misc/__init__.py b/mermithid/processors/misc/__init__.py index 3c9983d2..527b32d1 100644 --- a/mermithid/processors/misc/__init__.py +++ b/mermithid/processors/misc/__init__.py @@ -5,3 +5,4 @@ from .FrequencyEnergyConversionProcessor import FrequencyEnergyConversionProcessor from .FrequencyShifter import FrequencyShifter +from .SensitivityCurveProcessor import SensitivityCurveProcessor diff --git a/tests/Misc_test.py b/tests/Misc_test.py index d1f05019..94baf703 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -38,6 +38,18 @@ def test_FreqConversionTest(self): logger.info("Resulting energies: %s"%freq_proc.energies) + def test_SensitivityCurveProcessor(self): + from mermithid.processors.misc import SensitivityCurveProcessor + + + sens_config_dict = { + "config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_molecular_FSCD_V_eff_2.cfg", + "add_comparison_curve": False + } + sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() + if __name__ == '__main__': From d192c8b2bd10fb90a880106d0b186f0e51007758 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 17 Nov 2020 13:52:57 +0100 Subject: [PATCH 002/262] continuing and adding options --- .../misc/SensitivityCurveProcessor.py | 160 ++++++++++++------ tests/Misc_test.py | 13 +- 2 files changed, 117 insertions(+), 56 deletions(-) diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index a6ee7886..2f309f39 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -55,43 +55,74 @@ def InternalConfigure(self, params): ''' Configure ''' + # file paths self.config_file_path = reader.read_param(params, 'config_file_path', "required") self.comparison_config_file_path = reader.read_param(params, 'comparison_config_file_path', '') - self.add_comparison_curve = reader.read_param(params, 'add_comparison_curve', False) - self.add_track_length_axis = reader.read_param(params, 'add_track_length_axis', False) + self.plot_path = reader.read_param(params, 'plot_path', "required") + + + # labels + self.main_curve_upper_label = reader.read_param(params, 'main_curve_upper_label', r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") + self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") + self.comparison_curve_label = reader.read_param(params, 'comparison_curve_label', r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") + + + # options + self.comparison_curve = reader.read_param(params, 'comparison_curve', False) + + + # plot configurations + self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) + self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) + self.track_length_axis = reader.read_param(params, 'track_length_axis', True) + self.atomic_axis = reader.read_param(params, 'atomic_axis', False) + self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + + # goals + self.goals = reader.read_param(params, "goals", {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}) # setup sensitivities self.sens_main = Sensitivity(self.config_file_path) - if self.add_comparison_curve: + if self.comparison_curve: self.sens_ref = Sensitivity(self.comparison_config_file_path) - density_range = [1e14,1e21] - self.rhos = np.logspace(np.log10(density_range[0]), np.log10(density_range[1]), 100)/m**3 + # densities + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 return True + + def InternalRun(self): self.create_plot() - if self.add_track_length_axis: + # add second and third x axis for track lengths + if self.track_length_axis: self.add_track_length_axis() - #self.add_Phase_IV(color="k") - self.add_goal(2*eV, "Phase III (%.1f eV)"%(2*eV/eV)) - sens = self.sens_main - #self.add_arrow(sens) + + # add line for comparison using second config + if self.comparison_curve: + self.add_comparison_curve(label=self.comparison_curve_label) + self.add_arrow(self.sens_main) + + for key, value in self.goals.items(): + logger.info('Adding goal: {}'.format(key)) + self.add_goal(value*eV, key) + + for a, color in self.range(1, 8): - sig = sens.BToKeErr(a*ppm*T, sens.MagneticField.nominal_field) - sens.MagneticField.usefixedvalue = True - sens.MagneticField.default_systematic_smearing = sig - sens.MagneticField.default_systematic_uncertainty = 0.05*sig - self.add_sens_line(sens, color=color) - self.add_text(5e19, 5, r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") - self.add_text(5e19, 2.3, r"$\sigma_B = 1\,\mathrm{ppm}$") - self.save("./sensitivity_phase_III.png", Veff="2cm^3") + sig = self.sens_main.BToKeErr(a*ppm*T, self.sens_main.MagneticField.nominal_field) + self.sens_main.MagneticField.usefixedvalue = True + self.sens_main.MagneticField.default_systematic_smearing = sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + self.add_sens_line(self.sens_main, color=color) + self.add_text(5e19, 5, self.main_curve_upper_label) + self.add_text(5e19, 2.3, self.main_curve_lower_label) + self.save(self.plot_path) return True @@ -103,51 +134,69 @@ def create_plot(self): ax.set_xscale("log") ax.set_yscale("log") ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) - ax.set_ylim(1e-2, 1e2) - ax.set_xlabel(r"(atomic / molecular) number density $\rho\, /\, \mathrm{m}^{-3}$") + ax.set_ylim(self.ylim) + + if self.atomic_axis and self.molecular_axis: + axis_label = r"(atomic / molecular) number density $\rho\, /\, \mathrm{m}^{-3}$" + elif self.atomic_axis: + axis_label = r"(atomic) number density $\rho\, /\, \mathrm{m}^{-3}$" + elif self.molecular_axis: + axis_label = r"(molecular) number density $\rho\, /\, \mathrm{m}^{-3}$" + else: + axis_label = r"number density $\rho\, /\, \mathrm{m}^{-3}$" + + ax.set_xlabel(axis_label) ax.set_ylabel(r"90% CL $m_\beta$ / eV") def add_track_length_axis(self): - ax2 = self.ax.twiny() - ax2.set_xscale("log") - ax2.set_xlabel("(atomic) track length / s") - ax2.set_xlim(self.sens_IV.track_length(self.rhos[0])/s, - self.sens_IV.track_length(self.rhos[-1])/s) - - ax3 = self.ax.twiny() - ax3.spines["top"].set_position(("axes", 1.2)) - ax3.set_frame_on(True) - ax3.patch.set_visible(False) - for sp in ax3.spines.values(): - sp.set_visible(False) - ax3.spines["top"].set_visible(True) - ax3.set_xscale("log") - ax3.set_xlabel("(molecular) track length / s") - ax3.set_xlim(self.sens_III.track_length(self.rhos[0])/s, - self.sens_III.track_length(self.rhos[-1])/s) + if self.atomic_axis: + ax2 = self.ax.twiny() + ax2.set_xscale("log") + ax2.set_xlabel("(atomic) track length / s") + ax2.set_xlim(self.sens_ref.track_length(self.rhos[0])/s, + self.sens_ref.track_length(self.rhos[-1])/s) + + if self.molecular_axis: + ax3 = self.ax.twiny() + + if self.atomic_axis: + ax3.spines["top"].set_position(("axes", 1.2)) + ax3.set_frame_on(True) + ax3.patch.set_visible(False) + for sp in ax3.spines.values(): + sp.set_visible(False) + ax3.spines["top"].set_visible(True) + + ax3.set_xscale("log") + ax3.set_xlabel("(molecular) track length / s") + ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) + + else: + logger.warning('For track length axis configure to use molecular and/or atomic gas') self.fig.tight_layout() - def add_Phase_IV(self, **kwargs): - limit_IV = [self.sens_IV.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - self.opt_IV = np.argmin(limit_IV) - self.ax.plot(self.rhos*m**3, limit_IV, **kwargs) - self.ax.axvline(self.rhos[self.opt_IV]*m**3, ls=":", color="gray", alpha=0.4) + def add_comparison_curve(self, label, color='k'): + limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.opt_ref = np.argmin(limit) + self.ax.plot(self.rhos*m**3, limit, color=color) + self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) - self.ax.axhline(0.04, color="gray", ls="--") - self.ax.text(3e14, 0.044, "Phase IV (40 meV)") - self.ax.text(1.5e14, 0.11, r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") + #self.ax.axhline(0.04, color="gray", ls="--") + #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") + self.ax.text(1.5e14, 0.11, label) def add_arrow(self, sens): - if not hasattr(self, "opt_IV"): - self.add_Phase_IV() + if not hasattr(self, "opt_ref"): + self.add_comparison_curve() def get_relative(val, axis): xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() return (np.log10(val)-np.log10(xmin))/(np.log10(xmax)-np.log10(xmin)) - rho_IV = self.rhos[self.opt_IV] - track_length_IV = self.sens_IV.track_length(rho_IV) - track_length_III = self.sens_III.track_length(rho_IV) + rho_IV = self.rhos[self.opt_ref] + track_length_IV = self.sens_ref.track_length(rho_IV) + track_length_III = self.sens_main.track_length(rho_IV) rho_III = rho_IV*track_length_III/track_length_IV limit_III = sens.CL90(Experiment={"number_density": rho_III}) @@ -181,11 +230,12 @@ def range(self, start, stop): def save(self, savepath, **kwargs): self.fig.tight_layout() - keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) - metadata = {"Author": "René Reimann", - "Title": "Phase III neutrino mass sensitivity", - "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero. We use different Phase III design parameters and plot as a function of number density. As a comparison the Phase IV sensitivity is shown as well. The upper axis gives the corresponding track length for the assumed density. The arrow indicatess the Phase III point with the same track length as in the optimal point of Phase IV.", - "Keywords": keywords} + #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) + metadata = {"Author": "p8/mermithid", + "Title": "Neutrino mass sensitivity", + "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero." + } + #"Keywords": keywords} if savepath is not None: self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=200, metadata=metadata) self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) diff --git a/tests/Misc_test.py b/tests/Misc_test.py index 94baf703..638625d5 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -43,8 +43,19 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { + # required "config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_molecular_FSCD_V_eff_2.cfg", - "add_comparison_curve": False + "plot_path": "./sensitivity_curve.pdf", + # optional + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "y_limits": [1e-2, 1e2], + "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", + "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_V0_00_01.cfg", + } sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From b6c5d6bee1f1f2f9af16316d0b2b9f05a453306b Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 17 Nov 2020 19:30:35 +0100 Subject: [PATCH 003/262] moved sensitivity formulas to misc --- mermithid/misc/SensitivityFormulas.py | 298 ++++++++++++++++++ mermithid/misc/__init__.py | 1 + .../misc/SensitivityCurveProcessor.py | 290 +---------------- 3 files changed, 303 insertions(+), 286 deletions(-) create mode 100644 mermithid/misc/SensitivityFormulas.py diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py new file mode 100644 index 00000000..a3b1e69f --- /dev/null +++ b/mermithid/misc/SensitivityFormulas.py @@ -0,0 +1,298 @@ +''' +Summarize sensitivity formulas in class... +''' +import numpy as np +import configparser +from numpy import pi + +# Numericalunits is a package to handle units and some natural constants +# natural constants +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day +ppm = 1e-6 +ppb = 1e-9 + +from morpho.utilities import morphologging +logger = morphologging.getLogger(__name__) + +class NameSpace(object): + def __init__(self, iteritems): + if type(iteritems) == dict: + iteritems = iteritems.items() + for k, v in iteritems: + setattr(self, k.lower(), v) + def __getattribute__(self, item): + return object.__getattribute__(self, item.lower()) + + + +############################################################################### +class Sensitivity(object): + """ + Documentation: + * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 + * Talias sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 + * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 + * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 + """ + def __init__(self, config_path): + self.cfg = configparser.ConfigParser() + with open(config_path, 'r') as configfile: + self.cfg.read_file(configfile) + + # display configuration + logger.info("Config file content:") + for sect in self.cfg.sections(): + logger.info(' Section: {}'.format(sect)) + for k,v in self.cfg.items(sect): + logger.info(' {} = {}'.format(k,v)) + + + self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) + + self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) + self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) + if self.Experiment.atomic: + self.Tritium = self.Tritium_atomic + else: + self.Tritium = self.Tritium_molecular + self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) + self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) + self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) + self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) + self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + + # SENSITIVITY + def SignalRate(self): + """signal events in the energy interval before the endpoint, scale with DeltaE**3""" + signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.Tritium.last_1ev_fraction/self.Tritium.Livetime + if not self.Experiment.atomic: + signal_rate *= 2 + return signal_rate + + def DeltaEWidth(self): + """optimal energy bin width""" + labels, sigmas, deltas = self.get_systematics() + return np.sqrt(self.Experiment.background_rate_per_eV/self.SignalRate() + + 8*np.log(2)*(np.sum(sigmas**2))) + + def StatSens(self): + """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + sig_rate = self.SignalRate() + DeltaE = self.DeltaEWidth() + sens = 4/(6*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + +self.Experiment.background_rate_per_eV*self.Experiment.LiveTime/DeltaE) + return sens + + def SystSens(self): + """Pure systematic componenet to sensitivity""" + labels, sigmas, deltas = self.get_systematics() + sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + return sens + + def sensitivity(self, **kwargs): + for sect, options in kwargs.items(): + for opt, val in options.items(): + self.__dict__[sect].__dict__[opt] = val + + StatSens = self.StatSens() + SystSens = self.SystSens() + + # Standard deviation on a measurement of m_beta**2 assuming a mass of zero + sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + return sigma_m_beta_2 + + def CL90(self, **kwargs): + return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + + # PHYSICS Functions + def frequency(self, energy, magnetic_field): + # cyclotron frequency + gamma = lambda energy: energy/(me*c0**2) + 1 # E_kin / E_0 + 1 + frequency = e*magnetic_field/(2*np.pi*me)/gamma(energy) + return frequency + + def BToKeErr(self, BErr, B): + return e*BErr/(2*np.pi*self.frequency(self.Tritium.endpoint, B)/c0**2) + + def track_length(self, rho): + Ke = self.Tritium.endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke + return 1 / (rho * self.Tritium.crosssection_te*betae*c0) + + # SYSTEMATICS + + def get_systematics(self): + # Different types of uncertainty contributions + sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() + sigma_f, delta_sigma_f = self.syst_frequency_extraction() + sigma_B, delta_sigma_B = self.syst_magnetic_field() + sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() + sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() + + labels = ["Termal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] + sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] + deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] + + if not self.Experiment.atomic: + labels.append("Molecular final state") + sigmas.append(self.Tritium.ground_state_width) + deltas.append(self.Tritium.ground_state_width_uncertainty) + + return np.array(labels), np.array(sigmas), np.array(deltas) + + def print_statistics(self): + print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + + def print_systematics(self): + labels, sigmas, deltas = self.get_systematics() + + print() + for label, sigma, delta in zip(labels, sigmas, deltas): + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") + + def syst_doppler_broadening(self): + # estimated standard deviation of Doppler broadening distribution from + # translational motion of tritium atoms / molecules + # Predicted uncertainty on standard deviation, based on expected precision + # of temperature knowledge + if self.DopplerBroadening.UseFixedValue: + sigma = self.DopplerBroadening.Default_Systematic_Smearing + delta = self.DopplerBroadening.Default_Systematic_Uncertainty + return sigma, delta + + # termal doppler broardening + gasTemp = self.DopplerBroadening.gas_temperature + mass_T = self.Tritium.mass + endpoint = self.Tritium.endpoint + + # these factors are mainly neglidible in the recoil equation below + E_rec = 3.409 * eV # maximal value # same for molecular tritium? + mbeta = 0*eV # term neglidible + betanu = 1 # neutrinos are fast + # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu + # => expectation value of cosThetanu = 0.014 + cosThetaenu = 0.014 + + Ke = endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke + Emax = endpoint + me*c0**2 + Ee = endpoint + me*c0**2 + p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) + sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) + + delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + return sigma_trans, delta_trans + + def syst_frequency_extraction(self): + # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) + # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? + # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? + + if self.FrequencyExtraction.UseFixedValue: + sigma = self.FrequencyExtraction.Default_Systematic_Smearing + delta = self.FrequencyExtraction.Default_Systematic_Uncertainty + return sigma, delta + + ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound + ts = self.FrequencyExtraction.track_timestep + Gdot = self.FrequencyExtraction.track_onset_rate + Ke = self.Tritium.endpoint + + fEndpoint = self.frequency(self.Tritium.endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke + Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power + alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope + sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level + Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) + Nsteps = 1 / (self.Experiment.number_density * self.Tritium.crosssection_te*betae*c0*ts) # Number of timesteps of length ts + + # sigma_f from Cramer-Rao lower bound in Hz + sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) + + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) + # uncertainty in alpha + delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) + # uncetainty in sigma_f in Hz due to uncertainty in alpha + delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) + + # sigma_f from Cramer-Rao lower bound in eV + sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 + delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 + + # combined sigma_f in eV + sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) + + return sigma_f, delta_sigma_f + + def syst_magnetic_field(self): + if self.MagneticField.UseFixedValue: + sigma = self.MagneticField.Default_Systematic_Smearing + delta = self.MagneticField.Default_Systematic_Uncertainty + return sigma, delta + + B = self.MagneticField.nominal_field + if self.MagneticField.useinhomogenarity: + inhomogenarity = self.MagneticField.inhomogenarity + sigma = self.BToKeErr(inhomogenarity*B, B) + return sigma, 0.05*sigma + + BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability + delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution + + BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field + delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? + + Delta_t_since_calib = self.MagneticField.time_since_calibration + shiftBdot = self.MagneticField.shift_Bdot + smearBdot = self.MagneticField.smear_Bdot + delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot + delta_smearBdot = self.MagneticField.uncertainty_smearBdot + BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) + delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) + + rRecoErr = self.MagneticField.rRecoErr + delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr + + rRecoPhiErr = self.MagneticField.rRecoPhiErr + delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr + + rProbeErr = self.MagneticField.rProbeErr + delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr + + rProbePhiErr = self.MagneticField.rProbePhiErr + delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr + + Berr = np.sqrt(BMapErr**2 + + BFlatErr**2 + + BdotErr**2 + + rRecoErr**2 + + rRecoPhiErr**2 + + rProbeErr**2 + + rProbePhiErr**2) + + delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + + BFlatErr**2 * delta_BFlatErr**2 + + BdotErr**2 * delta_BdotErr**2 + + rRecoErr**2 * delta_rRecoErr**2 + + rRecoPhiErr**2 * delta_rRecoPhiErr**2 + + rProbeErr**2 * delta_rProbeErr**2 + + rProbePhiErr**2 * delta_rProbePhiErr**2) + + return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) + + def syst_missing_tracks(self): + if self.MissingTracks.UseFixedValue: + sigma = self.MissingTracks.Default_Systematic_Smearing + delta = self.MissingTracks.Default_Systematic_Uncertainty + return sigma, delta + + def syst_plasma_effects(self): + if self.PlasmaEffects.UseFixedValue: + sigma = self.PlasmaEffects.Default_Systematic_Smearing + delta = self.PlasmaEffects.Default_Systematic_Uncertainty + return sigma, delta + else: + raise NotImplementedError() \ No newline at end of file diff --git a/mermithid/misc/__init__.py b/mermithid/misc/__init__.py index 0dc77499..cabb6457 100644 --- a/mermithid/misc/__init__.py +++ b/mermithid/misc/__init__.py @@ -9,3 +9,4 @@ from . import FakeTritiumDataFunctions from . import ConversionFunctions from . import KrLineshapeFunctions +from . import SensitivityFormulas diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index 2f309f39..6895808b 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -13,11 +13,10 @@ import matplotlib import matplotlib.pyplot as plt import numpy as np -import configparser import argparse import sys import os -from numpy import pi + # Numericalunits is a package to handle units and some natural constants # natural constants @@ -31,7 +30,7 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor -from mermithid.misc import Constants +from mermithid.misc.SensitivityFormulas import Sensitivity #from mermithid.misc import ConversionFunctions logger = morphologging.getLogger(__name__) @@ -85,9 +84,11 @@ def InternalConfigure(self, params): # setup sensitivities self.sens_main = Sensitivity(self.config_file_path) + self.sens_main_is_atomic = self.sens_main.Experiment.atomic if self.comparison_curve: self.sens_ref = Sensitivity(self.comparison_config_file_path) + self.sens_ref_is_atomic = self.sens_ref.Experiment.atomic # densities self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 @@ -241,286 +242,3 @@ def save(self, savepath, **kwargs): self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) -############################################################################### - -class NameSpace(object): - def __init__(self, iteritems): - if type(iteritems) == dict: - iteritems = iteritems.items() - for k, v in iteritems: - setattr(self, k.lower(), v) - def __getattribute__(self, item): - return object.__getattribute__(self, item.lower()) - - - -############################################################################### -class Sensitivity(object): - """ - Documentation: - * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 - * Talias sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 - * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 - * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 - """ - def __init__(self, config_path): - self.cfg = configparser.ConfigParser() - with open(config_path, 'r') as configfile: - self.cfg.read_file(configfile) - - # display configuration - logger.info("Config file content:") - for sect in self.cfg.sections(): - logger.info(' Section: {}'.format(sect)) - for k,v in self.cfg.items(sect): - logger.info(' {} = {}'.format(k,v)) - - - self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - - self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) - self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) - if self.Experiment.atomic: - self.Tritium = self.Tritium_atomic - else: - self.Tritium = self.Tritium_molecular - self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) - self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) - self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) - self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) - self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) - - # SENSITIVITY - def SignalRate(self): - """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.Tritium.last_1ev_fraction/self.Tritium.Livetime - if not self.Experiment.atomic: - signal_rate *= 2 - return signal_rate - - def DeltaEWidth(self): - """optimal energy bin width""" - labels, sigmas, deltas = self.get_systematics() - return np.sqrt(self.Experiment.background_rate_per_eV/self.SignalRate() - + 8*np.log(2)*(np.sum(sigmas**2))) - - def StatSens(self): - """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" - sig_rate = self.SignalRate() - DeltaE = self.DeltaEWidth() - sens = 4/(6*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - +self.Experiment.background_rate_per_eV*self.Experiment.LiveTime/DeltaE) - return sens - - def SystSens(self): - """Pure systematic componenet to sensitivity""" - labels, sigmas, deltas = self.get_systematics() - sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) - return sens - - def sensitivity(self, **kwargs): - for sect, options in kwargs.items(): - for opt, val in options.items(): - self.__dict__[sect].__dict__[opt] = val - - StatSens = self.StatSens() - SystSens = self.SystSens() - - # Standard deviation on a measurement of m_beta**2 assuming a mass of zero - sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) - return sigma_m_beta_2 - - def CL90(self, **kwargs): - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - - # PHYSICS Functions - def frequency(self, energy, magnetic_field): - # cyclotron frequency - gamma = lambda energy: energy/(me*c0**2) + 1 # E_kin / E_0 + 1 - frequency = e*magnetic_field/(2*np.pi*me)/gamma(energy) - return frequency - - def BToKeErr(self, BErr, B): - return e*BErr/(2*np.pi*self.frequency(self.Tritium.endpoint, B)/c0**2) - - def track_length(self, rho): - Ke = self.Tritium.endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - return 1 / (rho * self.Tritium.crosssection_te*betae*c0) - - # SYSTEMATICS - - def get_systematics(self): - # Different types of uncertainty contributions - sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() - sigma_f, delta_sigma_f = self.syst_frequency_extraction() - sigma_B, delta_sigma_B = self.syst_magnetic_field() - sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() - sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() - - labels = ["Termal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] - sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] - deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] - - if not self.Experiment.atomic: - labels.append("Molecular final state") - sigmas.append(self.Tritium.ground_state_width) - deltas.append(self.Tritium.ground_state_width_uncertainty) - - return np.array(labels), np.array(sigmas), np.array(deltas) - - def print_statistics(self): - print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - - def print_systematics(self): - labels, sigmas, deltas = self.get_systematics() - - print() - for label, sigma, delta in zip(labels, sigmas, deltas): - print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") - - def syst_doppler_broadening(self): - # estimated standard deviation of Doppler broadening distribution from - # translational motion of tritium atoms / molecules - # Predicted uncertainty on standard deviation, based on expected precision - # of temperature knowledge - if self.DopplerBroadening.UseFixedValue: - sigma = self.DopplerBroadening.Default_Systematic_Smearing - delta = self.DopplerBroadening.Default_Systematic_Uncertainty - return sigma, delta - - # termal doppler broardening - gasTemp = self.DopplerBroadening.gas_temperature - mass_T = self.Tritium.mass - endpoint = self.Tritium.endpoint - - # these factors are mainly neglidible in the recoil equation below - E_rec = 3.409 * eV # maximal value # same for molecular tritium? - mbeta = 0*eV # term neglidible - betanu = 1 # neutrinos are fast - # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu - # => expectation value of cosThetanu = 0.014 - cosThetaenu = 0.014 - - Ke = endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke - Emax = endpoint + me*c0**2 - Ee = endpoint + me*c0**2 - p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) - sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) - - delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) - return sigma_trans, delta_trans - - def syst_frequency_extraction(self): - # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) - # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? - # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? - - if self.FrequencyExtraction.UseFixedValue: - sigma = self.FrequencyExtraction.Default_Systematic_Smearing - delta = self.FrequencyExtraction.Default_Systematic_Uncertainty - return sigma, delta - - ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound - ts = self.FrequencyExtraction.track_timestep - Gdot = self.FrequencyExtraction.track_onset_rate - Ke = self.Tritium.endpoint - - fEndpoint = self.frequency(self.Tritium.endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power - alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope - sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level - Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) - Nsteps = 1 / (self.Experiment.number_density * self.Tritium.crosssection_te*betae*c0*ts) # Number of timesteps of length ts - - # sigma_f from Cramer-Rao lower bound in Hz - sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) - + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) - # uncertainty in alpha - delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) - # uncetainty in sigma_f in Hz due to uncertainty in alpha - delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) - - # sigma_f from Cramer-Rao lower bound in eV - sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 - delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 - - # combined sigma_f in eV - sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) - delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) - - return sigma_f, delta_sigma_f - - def syst_magnetic_field(self): - if self.MagneticField.UseFixedValue: - sigma = self.MagneticField.Default_Systematic_Smearing - delta = self.MagneticField.Default_Systematic_Uncertainty - return sigma, delta - - B = self.MagneticField.nominal_field - if self.MagneticField.useinhomogenarity: - inhomogenarity = self.MagneticField.inhomogenarity - sigma = self.BToKeErr(inhomogenarity*B, B) - return sigma, 0.05*sigma - - BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability - delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution - - BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field - delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? - - Delta_t_since_calib = self.MagneticField.time_since_calibration - shiftBdot = self.MagneticField.shift_Bdot - smearBdot = self.MagneticField.smear_Bdot - delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot - delta_smearBdot = self.MagneticField.uncertainty_smearBdot - BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) - delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) - - rRecoErr = self.MagneticField.rRecoErr - delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr - - rRecoPhiErr = self.MagneticField.rRecoPhiErr - delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr - - rProbeErr = self.MagneticField.rProbeErr - delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr - - rProbePhiErr = self.MagneticField.rProbePhiErr - delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr - - Berr = np.sqrt(BMapErr**2 + - BFlatErr**2 + - BdotErr**2 + - rRecoErr**2 + - rRecoPhiErr**2 + - rProbeErr**2 + - rProbePhiErr**2) - - delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + - BFlatErr**2 * delta_BFlatErr**2 + - BdotErr**2 * delta_BdotErr**2 + - rRecoErr**2 * delta_rRecoErr**2 + - rRecoPhiErr**2 * delta_rRecoPhiErr**2 + - rProbeErr**2 * delta_rProbeErr**2 + - rProbePhiErr**2 * delta_rProbePhiErr**2) - - return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) - - def syst_missing_tracks(self): - if self.MissingTracks.UseFixedValue: - sigma = self.MissingTracks.Default_Systematic_Smearing - delta = self.MissingTracks.Default_Systematic_Uncertainty - return sigma, delta - - def syst_plasma_effects(self): - if self.PlasmaEffects.UseFixedValue: - sigma = self.PlasmaEffects.Default_Systematic_Smearing - delta = self.PlasmaEffects.Default_Systematic_Uncertainty - return sigma, delta - else: - raise NotImplementedError() - -############################################################################### \ No newline at end of file From ad031dfa7727b7dc9b49e74ff033bc80ff6b771e Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 17 Nov 2020 21:03:00 +0100 Subject: [PATCH 004/262] now any config can be molecular or atomic. and B can be single number or list. --- .../misc/SensitivityCurveProcessor.py | 63 +++++++++++++++---- tests/Misc_test.py | 1 + 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index 6895808b..b874445f 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -68,20 +68,23 @@ def InternalConfigure(self, params): # options self.comparison_curve = reader.read_param(params, 'comparison_curve', False) + self.B = reader.read_param(params, 'B', 7e-6) + self.B_uncertainty = reader.read_param(params, 'B_uncertainty', 0.05) # plot configurations + self.figsize = reader.read_param(params, 'figsize', (6,6)) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + # goals self.goals = reader.read_param(params, "goals", {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}) - # setup sensitivities self.sens_main = Sensitivity(self.config_file_path) self.sens_main_is_atomic = self.sens_main.Experiment.atomic @@ -90,6 +93,27 @@ def InternalConfigure(self, params): self.sens_ref = Sensitivity(self.comparison_config_file_path) self.sens_ref_is_atomic = self.sens_ref.Experiment.atomic + # check atomic and molecular + if self.molecular_axis: + if not self.sens_main_is_atomic: + self.molecular_sens = self.sens_main + logger.info("Main curve is molecular") + elif not self.sens_ref_is_atomic: + self.molecular_sens = self.sens_ref + logger.info("Comparison curve is molecular") + else: + raise ValueError("No experiment is configured to be molecular") + + if self.atomic_axis: + if self.sens_main_is_atomic: + self.atomic_sens = self.sens_main + logger.info("Main curve is atomic") + elif self.sens_ref_is_atomic: + self.atomic_sens = self.sens_ref + logger.info("Comparison curve is atomic") + else: + raise ValueError("No experiment is configured to be atomic") + # densities self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 @@ -114,15 +138,28 @@ def InternalRun(self): logger.info('Adding goal: {}'.format(key)) self.add_goal(value*eV, key) + # if B is list plot line for each B + if isinstance(self.B, list) or isinstance(self.B, np.ndarray): + N = len(self.B) + for a, color in self.range(1, N): + sig = self.sens_main.BToKeErr(self.B[a]*T, self.sens_main.MagneticField.nominal_field) + self.sens_main.MagneticField.usefixedvalue = True + self.sens_main.MagneticField.default_systematic_smearing = sig + self.sens_main.MagneticField.default_systematic_uncertainty = self.B_uncertainty*sig + self.add_sens_line(self.sens_main, color=color) + self.add_text(5e19, 5, self.main_curve_upper_label) + self.add_text(5e19, 2.3, self.main_curve_lower_label) - for a, color in self.range(1, 8): - sig = self.sens_main.BToKeErr(a*ppm*T, self.sens_main.MagneticField.nominal_field) + else: + sig = self.sens_main.BToKeErr(self.B*T, self.sens_main.MagneticField.nominal_field) self.sens_main.MagneticField.usefixedvalue = True self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig - self.add_sens_line(self.sens_main, color=color) - self.add_text(5e19, 5, self.main_curve_upper_label) - self.add_text(5e19, 2.3, self.main_curve_lower_label) + self.sens_main.MagneticField.default_systematic_uncertainty = self.B_uncertainty*sig + self.add_sens_line(self.sens_main, color='blue') + self.add_text(5e19, 5, self.main_curve_upper_label) + + + # save plot self.save(self.plot_path) return True @@ -130,7 +167,7 @@ def InternalRun(self): def create_plot(self): # setup axis - self.fig, self.ax = plt.subplots(figsize=(6,6)) + self.fig, self.ax = plt.subplots(figsize=self.figsize) ax = self.ax ax.set_xscale("log") ax.set_yscale("log") @@ -154,8 +191,8 @@ def add_track_length_axis(self): ax2 = self.ax.twiny() ax2.set_xscale("log") ax2.set_xlabel("(atomic) track length / s") - ax2.set_xlim(self.sens_ref.track_length(self.rhos[0])/s, - self.sens_ref.track_length(self.rhos[-1])/s) + ax2.set_xlim(self.atomic_sens.track_length(self.rhos[0])/s, + self.atomic_sens.track_length(self.rhos[-1])/s) if self.molecular_axis: ax3 = self.ax.twiny() @@ -170,11 +207,11 @@ def add_track_length_axis(self): ax3.set_xscale("log") ax3.set_xlabel("(molecular) track length / s") - ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, - self.sens_main.track_length(self.rhos[-1])/s) + ax3.set_xlim(self.molecular_sens.track_length(self.rhos[0])/s, + self.molecular_sens.track_length(self.rhos[-1])/s) else: - logger.warning('For track length axis configure to use molecular and/or atomic gas') + logger.warning("No track length axis added since neither atomic nor molecular was requested") self.fig.tight_layout() def add_comparison_curve(self, label, color='k'): diff --git a/tests/Misc_test.py b/tests/Misc_test.py index 638625d5..62ba141c 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -55,6 +55,7 @@ def test_SensitivityCurveProcessor(self): "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, "comparison_config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_V0_00_01.cfg", + "B": np.arange(1, 8)*1e-6 } sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") From a93a4224a59b697f113d2711f60dbf39b65fac01 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 17 Nov 2020 21:05:31 +0100 Subject: [PATCH 005/262] removed unnecessary imports --- mermithid/processors/misc/SensitivityCurveProcessor.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index b874445f..f88b936b 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -13,9 +13,6 @@ import matplotlib import matplotlib.pyplot as plt import numpy as np -import argparse -import sys -import os # Numericalunits is a package to handle units and some natural constants @@ -31,7 +28,7 @@ from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor from mermithid.misc.SensitivityFormulas import Sensitivity -#from mermithid.misc import ConversionFunctions + logger = morphologging.getLogger(__name__) From caead3c87139a7a99bf71c7403004e373ad3bbbe Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sat, 20 Feb 2021 17:12:27 +0100 Subject: [PATCH 006/262] added print of signal rate --- .../misc/SensitivityCurveProcessor.py | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index f88b936b..c44dbade 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -129,7 +129,7 @@ def InternalRun(self): # add line for comparison using second config if self.comparison_curve: self.add_comparison_curve(label=self.comparison_curve_label) - self.add_arrow(self.sens_main) + #self.add_arrow(self.sens_main) for key, value in self.goals.items(): logger.info('Adding goal: {}'.format(key)) @@ -159,6 +159,22 @@ def InternalRun(self): # save plot self.save(self.plot_path) + # print number of events + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.opt_ref = np.argmin(limit) + + rho_opt = self.rhos[self.opt_ref] + logger.info('Main curve (veff = {} cm**3, rho = {} /m**3):'.format(self.sens_main.Experiment.v_eff/(cm**3), rho_opt*(m**3))) + logger.info('Sensitivitiy limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.Experiment.v_eff)) + logger.info('Total signal: {}'.format(rho_opt*self.sens_main.Experiment.v_eff* + self.sens_main.Experiment.LiveTime/ + self.sens_main.Tritium.Livetime*2)) + logger.info('Signal in last eV: {}'.format(self.sens_main.Tritium.last_1ev_fraction*eV**3* + rho_opt*self.sens_main.Experiment.v_eff* + self.sens_main.Experiment.LiveTime/ + self.sens_main.Tritium.Livetime*2)) + return True @@ -214,6 +230,14 @@ def add_track_length_axis(self): def add_comparison_curve(self, label, color='k'): limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] self.opt_ref = np.argmin(limit) + + rho_opt = self.rhos[self.opt_ref] + logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.Experiment.v_eff/(m**3))) + logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff)) + logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff* + self.sens_ref.Experiment.LiveTime/ + self.sens_ref.Tritium.Livetime)) + self.ax.plot(self.rhos*m**3, limit, color=color) self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) From f47213608fea31d548e8a2ec528623d5646fc6d6 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 18 Jun 2021 16:42:45 -0700 Subject: [PATCH 007/262] working with current config files in scripts/rreimann --- mermithid/misc/SensitivityFormulas.py | 164 +++++++++++++++--- .../misc/SensitivityCurveProcessor.py | 8 +- tests/Misc_test.py | 4 +- 3 files changed, 149 insertions(+), 27 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index a3b1e69f..f1ba31d3 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -8,11 +8,44 @@ # Numericalunits is a package to handle units and some natural constants # natural constants from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W from numericalunits import hour, year, day +from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck + +T0 = -273.15*K + +tritium_livetime = 5.605e8*s +tritium_mass_atomic = 3.016* amu *c0**2 +tritium_electron_crosssection_atomic = 1.1e-22*m**2 +tritium_endpoint_atomic = 18563.251*eV +last_1ev_fraction_atomic = 2.067914e-13/eV**3 + +tritium_mass_molecular = 6.032099 * amu *c0**2 +tritium_electron_crosssection_molecular = 3.487*1e-22*m**2 +tritium_endpoint_molecular = 18573.24*eV +last_1ev_fraction_molecular = 1.67364e-13/eV**3 + +ground_state_width = 0.436 * eV +ground_state_width_uncertainty = 0.01*0.436*eV + +gyro_mag_ratio_proton = 42.577*MHz/T + +# units that do not show up in numericalunits +# missing pre-factors +fW = W*1e-15 + +# unitless units, relative fractions +pc = 0.01 ppm = 1e-6 ppb = 1e-9 +ppt = 1e-12 +ppq = 1e-15 + +# radian and degree which are also not really units +rad = 1 +deg = np.pi/180 + from morpho.utilities import morphologging logger = morphologging.getLogger(__name__) @@ -26,6 +59,35 @@ def __init__(self, iteritems): def __getattribute__(self, item): return object.__getattribute__(self, item.lower()) +############################################################################## +# CRES functions +def gamma(kin_energy): + return kin_energy/(me*c0**2) + 1 + +def beta(kin_energy): + # electron speed at kin_energy + return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) + +def frequency(kin_energy, magnetic_field): + # cyclotron frequency + return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field + +def kin_energy(freq, magnetic_field): + return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) + +def rad_power(kin_energy, pitch, magnetic_field): + # electron radiation power + f = frequency(kin_energy, magnetic_field) + b = beta(kin_energy) + Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) + return Pe + +def track_length(rho, kin_energy=None, molecular=True): + if kin_energy is None: + kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic + crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic + return 1 / (rho * crosssect * beta(kin_energy) * c0) + ############################################################################### @@ -52,12 +114,26 @@ def __init__(self, config_path): self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) - self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) + # self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) + # self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) + # if self.Experiment.atomic: + # self.Tritium = self.Tritium_atomic + # else: + # self.Tritium = self.Tritium_molecular + + self.T_livetime = tritium_livetime if self.Experiment.atomic: - self.Tritium = self.Tritium_atomic + self.T_mass = tritium_mass_atomic + self.Te_crosssection = tritium_electron_crosssection_atomic + self.T_endpoint = tritium_endpoint_atomic + self.last_1ev_fraction = last_1ev_fraction_atomic else: - self.Tritium = self.Tritium_molecular + self.T_mass = tritium_mass_molecular + self.Te_crosssection = tritium_electron_crosssection_molecular + self.T_endpoint = tritium_endpoint_molecular + self.last_1ev_fraction = last_1ev_fraction_molecular + + self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) @@ -67,7 +143,7 @@ def __init__(self, config_path): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.Tritium.last_1ev_fraction/self.Tritium.Livetime + signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.T_livetime if not self.Experiment.atomic: signal_rate *= 2 return signal_rate @@ -115,12 +191,12 @@ def frequency(self, energy, magnetic_field): return frequency def BToKeErr(self, BErr, B): - return e*BErr/(2*np.pi*self.frequency(self.Tritium.endpoint, B)/c0**2) + return e*BErr/(2*np.pi*self.frequency(self.T_endpoint, B)/c0**2) def track_length(self, rho): - Ke = self.Tritium.endpoint + Ke = self.T_endpoint betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - return 1 / (rho * self.Tritium.crosssection_te*betae*c0) + return 1 / (rho * self.Te_crosssection*betae*c0) # SYSTEMATICS @@ -138,8 +214,8 @@ def get_systematics(self): if not self.Experiment.atomic: labels.append("Molecular final state") - sigmas.append(self.Tritium.ground_state_width) - deltas.append(self.Tritium.ground_state_width_uncertainty) + sigmas.append(ground_state_width) + deltas.append(ground_state_width_uncertainty) return np.array(labels), np.array(sigmas), np.array(deltas) @@ -165,8 +241,8 @@ def syst_doppler_broadening(self): # termal doppler broardening gasTemp = self.DopplerBroadening.gas_temperature - mass_T = self.Tritium.mass - endpoint = self.Tritium.endpoint + mass_T = self.T_mass + endpoint = self.T_endpoint # these factors are mainly neglidible in the recoil equation below E_rec = 3.409 * eV # maximal value # same for molecular tritium? @@ -186,28 +262,33 @@ def syst_doppler_broadening(self): delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) return sigma_trans, delta_trans + def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? - # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? + # Are we double counting the effect of magnetic field uncertainty here? + # Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? if self.FrequencyExtraction.UseFixedValue: sigma = self.FrequencyExtraction.Default_Systematic_Smearing delta = self.FrequencyExtraction.Default_Systematic_Uncertainty return sigma, delta - ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound + # Cramer-Rao lower bound / how much worse are we than the lower bound + ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor ts = self.FrequencyExtraction.track_timestep + # "This is apparent in the case of resonant patch antennas and cavities, in which the time scale of the signal onset is set by the Q-factor of the resonant structure." + # You can get it from the finite impulse response of the antennas from HFSS Gdot = self.FrequencyExtraction.track_onset_rate - Ke = self.Tritium.endpoint - fEndpoint = self.frequency(self.Tritium.endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power + fEndpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) + betae = beta(self.T_endpoint) + Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope - sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level + # quantum limited noise + sigNoise = np.sqrt((2*pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) - Nsteps = 1 / (self.Experiment.number_density * self.Tritium.crosssection_te*betae*c0*ts) # Number of timesteps of length ts + Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts # sigma_f from Cramer-Rao lower bound in Hz sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) @@ -227,6 +308,47 @@ def syst_frequency_extraction(self): return sigma_f, delta_sigma_f + # def syst_frequency_extraction(self): + # # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) + # # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? + # # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? + + # if self.FrequencyExtraction.UseFixedValue: + # sigma = self.FrequencyExtraction.Default_Systematic_Smearing + # delta = self.FrequencyExtraction.Default_Systematic_Uncertainty + # return sigma, delta + + # ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound + # ts = self.FrequencyExtraction.track_timestep + # Gdot = self.FrequencyExtraction.track_onset_rate + # Ke = self.T_endpoint + + # fEndpoint = self.frequency(self.T_endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint + # betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke + # Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power + # alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope + # sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level + # Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) + # Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts + + # # sigma_f from Cramer-Rao lower bound in Hz + # sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) + # + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) + # # uncertainty in alpha + # delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) + # # uncetainty in sigma_f in Hz due to uncertainty in alpha + # delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) + + # # sigma_f from Cramer-Rao lower bound in eV + # sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 + # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 + + # # combined sigma_f in eV + # sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) + + # return sigma_f, delta_sigma_f + def syst_magnetic_field(self): if self.MagneticField.UseFixedValue: sigma = self.MagneticField.Default_Systematic_Smearing diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index c44dbade..309f4d42 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -169,11 +169,11 @@ def InternalRun(self): logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.Experiment.v_eff)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ - self.sens_main.Tritium.Livetime*2)) - logger.info('Signal in last eV: {}'.format(self.sens_main.Tritium.last_1ev_fraction*eV**3* + self.sens_main.T_livetime*2)) + logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ - self.sens_main.Tritium.Livetime*2)) + self.sens_main.T_livetime*2)) return True @@ -236,7 +236,7 @@ def add_comparison_curve(self, label, color='k'): logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff)) logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff* self.sens_ref.Experiment.LiveTime/ - self.sens_ref.Tritium.Livetime)) + self.sens_ref.T_livetime)) self.ax.plot(self.rhos*m**3, limit, color=color) self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) diff --git a/tests/Misc_test.py b/tests/Misc_test.py index 62ba141c..8622da26 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -44,7 +44,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_molecular_FSCD_V_eff_2.cfg", + "config_file_path": "/home/chrischtel/repos/scripts/rreimann/data/Sensitivity/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", "plot_path": "./sensitivity_curve.pdf", # optional "track_length_axis": True, @@ -54,7 +54,7 @@ def test_SensitivityCurveProcessor(self): "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/home/chrischtel/repos/scripts/rreimann/SensitivityCalculation/Config_V0_00_01.cfg", + "comparison_config_file_path": "/home/chrischtel/repos/scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", "B": np.arange(1, 8)*1e-6 } From 1ac668b668c41f3afb2b7aa74a33f9945c4f84da Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sat, 13 Nov 2021 23:31:37 -0800 Subject: [PATCH 008/262] using config files from termite and scripts --- tests/Misc_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Misc_test.py b/tests/Misc_test.py index 8622da26..377f555b 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -44,17 +44,17 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/home/chrischtel/repos/scripts/rreimann/data/Sensitivity/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", "plot_path": "./sensitivity_curve.pdf", # optional "track_length_axis": True, "molecular_axis": True, - "atomic_axis": True, + "atomic_axis": False, "y_limits": [1e-2, 1e2], "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, - "comparison_curve": True, - "comparison_config_file_path": "/home/chrischtel/repos/scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", + "comparison_curve": False, + "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", "B": np.arange(1, 8)*1e-6 } @@ -78,4 +78,4 @@ def test_SensitivityCurveProcessor(self): stderr_lb=args.stderr_verbosity, propagate=False) - unittest.main() \ No newline at end of file + unittest.main() From b9d893b489602c6b408eb6ed2afd70349049cdc5 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Mon, 15 Nov 2021 22:30:21 -0500 Subject: [PATCH 009/262] Added count rate dependence on gas composition --- mermithid/misc/SensitivityFormulas.py | 24 ++++++++++++++++++- .../misc/SensitivityCurveProcessor.py | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index f1ba31d3..e9ca3606 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -145,7 +145,8 @@ def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.T_livetime if not self.Experiment.atomic: - signal_rate *= 2 + avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) + signal_rate *= avg_n_T_atoms return signal_rate def DeltaEWidth(self): @@ -183,7 +184,28 @@ def sensitivity(self, **kwargs): def CL90(self, **kwargs): return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + # PHYSICS Functions + + def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas_fractions): + """ + Given gas composition info (H2 vs. other gases, and how much of each H2-type isotopolog), returns an average number of tritium atoms per gas particle. + + Inputs: + - gas_fractions: dict of composition fractions of each gas (different from scatter fractions!); all H2 isotopologs are combined under key 'H2' + - H2_type_gas_fractions: dict with fraction of each isotopolog, out of total amount of H2 + """ + H2_iso_avg_num = 0 + for (key, val) in H2_type_gas_fractions.items(): + if key=='T2': + H2_iso_avg_num += 2*val + elif key=='HT' or key=='DT': + H2_iso_avg_num += val + elif key=='H2' or key=='HD' or key=='D2': + pass + return gas_fractions['H2']*H2_iso_avg_num + + def frequency(self, energy, magnetic_field): # cyclotron frequency gamma = lambda energy: energy/(me*c0**2) + 1 # E_kin / E_0 + 1 diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index 309f4d42..6d73bc55 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -165,7 +165,7 @@ def InternalRun(self): rho_opt = self.rhos[self.opt_ref] logger.info('Main curve (veff = {} cm**3, rho = {} /m**3):'.format(self.sens_main.Experiment.v_eff/(cm**3), rho_opt*(m**3))) - logger.info('Sensitivitiy limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('Sensitivity limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.Experiment.v_eff)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ From f3cf704611bed60fd60a7afd789edda755b914e5 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 20:47:52 +0000 Subject: [PATCH 010/262] removed commented out block that is a doublicate from the function definition above. Added some comments --- mermithid/misc/SensitivityFormulas.py | 67 +++++++-------------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index f1ba31d3..06eb1e31 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -1,5 +1,10 @@ ''' -Summarize sensitivity formulas in class... +Class calculating neutrino mass sensitivities based on analytic formulas from CDR. +Author: R. Reimann, C. Claessens +Date:11/17/2020 + +The statistical method and formulars are described in +CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np import configparser @@ -114,13 +119,6 @@ def __init__(self, config_path): self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - # self.Tritium_atomic = NameSpace({opt: eval(self.cfg.get('Tritium_atomic', opt)) for opt in self.cfg.options('Tritium_atomic')}) - # self.Tritium_molecular = NameSpace({opt: eval(self.cfg.get('Tritium_molecular', opt)) for opt in self.cfg.options('Tritium_molecular')}) - # if self.Experiment.atomic: - # self.Tritium = self.Tritium_atomic - # else: - # self.Tritium = self.Tritium_molecular - self.T_livetime = tritium_livetime if self.Experiment.atomic: self.T_mass = tritium_mass_atomic @@ -169,6 +167,11 @@ def SystSens(self): return sens def sensitivity(self, **kwargs): + """Combined statisical and systematic uncertainty. + Using kwargs settings in namespaces can be changed. + Example how to change number density which lives in namespace Experiment: + self.sensitivity(Experiment={"number_density": rho}) + """ for sect, options in kwargs.items(): for opt, val in options.items(): self.__dict__[sect].__dict__[opt] = val @@ -181,12 +184,15 @@ def sensitivity(self, **kwargs): return sigma_m_beta_2 def CL90(self, **kwargs): + """ Gives 90% CL upper limit on neutrino mass.""" + # 90% of gaussian are contained in +-1.64 sigma region return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) # PHYSICS Functions def frequency(self, energy, magnetic_field): # cyclotron frequency - gamma = lambda energy: energy/(me*c0**2) + 1 # E_kin / E_0 + 1 + # gamma = E_kin/E_0 + 1 + gamma = lambda energy: energy/(me*c0**2) + 1 frequency = e*magnetic_field/(2*np.pi*me)/gamma(energy) return frequency @@ -308,47 +314,6 @@ def syst_frequency_extraction(self): return sigma_f, delta_sigma_f - # def syst_frequency_extraction(self): - # # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) - # # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? - # # Are we double counting the effect of magnetic field uncertainty here? Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? - - # if self.FrequencyExtraction.UseFixedValue: - # sigma = self.FrequencyExtraction.Default_Systematic_Smearing - # delta = self.FrequencyExtraction.Default_Systematic_Uncertainty - # return sigma, delta - - # ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor # Cramer-Rao lower bound / how much worse are we than the lower bound - # ts = self.FrequencyExtraction.track_timestep - # Gdot = self.FrequencyExtraction.track_onset_rate - # Ke = self.T_endpoint - - # fEndpoint = self.frequency(self.T_endpoint, self.MagneticField.nominal_field) # cyclotron frequency at the endpoint - # betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - # Pe = 2*np.pi*(e*fEndpoint*betae*np.sin(self.FrequencyExtraction.pitch_angle))**2/(3*eps0*c0*(1-(betae)**2)) # electron radiation power - # alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope - # sigNoise = np.sqrt(kB*self.FrequencyExtraction.noise_temperature/ts) # noise level - # Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) - # Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts - - # # sigma_f from Cramer-Rao lower bound in Hz - # sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) - # + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) - # # uncertainty in alpha - # delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) - # # uncetainty in sigma_f in Hz due to uncertainty in alpha - # delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) - - # # sigma_f from Cramer-Rao lower bound in eV - # sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 - # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 - - # # combined sigma_f in eV - # sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) - # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) - - # return sigma_f, delta_sigma_f - def syst_magnetic_field(self): if self.MagneticField.UseFixedValue: sigma = self.MagneticField.Default_Systematic_Smearing @@ -417,4 +382,4 @@ def syst_plasma_effects(self): delta = self.PlasmaEffects.Default_Systematic_Uncertainty return sigma, delta else: - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() From 524663670e427e9b437bb4044e658532d7298153 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 20:53:43 +0000 Subject: [PATCH 011/262] use general CRES formulars also in class. Removes doublicate. Only reimplement if some values get fixed. --- mermithid/misc/SensitivityFormulas.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 06eb1e31..3edeac53 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -189,20 +189,11 @@ def CL90(self, **kwargs): return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) # PHYSICS Functions - def frequency(self, energy, magnetic_field): - # cyclotron frequency - # gamma = E_kin/E_0 + 1 - gamma = lambda energy: energy/(me*c0**2) + 1 - frequency = e*magnetic_field/(2*np.pi*me)/gamma(energy) - return frequency - def BToKeErr(self, BErr, B): - return e*BErr/(2*np.pi*self.frequency(self.T_endpoint, B)/c0**2) + return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) def track_length(self, rho): - Ke = self.T_endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) # electron speed at energy Ke - return 1 / (rho * self.Te_crosssection*betae*c0) + return track_length(rho, self.T_endpoint, not self.Experiment.atomic) # SYSTEMATICS From c7a3226268a48abb7700fc34581e0a484d1cb1c8 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 20:57:07 +0000 Subject: [PATCH 012/262] Make morpho optional. Dependence on Morpho is only for logging. Making morpho optional allows to use this file also outside mermithid and morph properly installed. Making morpho optional by making its import and calls in a try-except block. --- mermithid/misc/SensitivityFormulas.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 3edeac53..adda8b63 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -52,8 +52,11 @@ deg = np.pi/180 -from morpho.utilities import morphologging -logger = morphologging.getLogger(__name__) +try: + from morpho.utilities import morphologging + logger = morphologging.getLogger(__name__) +except: + print("Run without morpho!") class NameSpace(object): def __init__(self, iteritems): @@ -110,12 +113,14 @@ def __init__(self, config_path): self.cfg.read_file(configfile) # display configuration - logger.info("Config file content:") - for sect in self.cfg.sections(): - logger.info(' Section: {}'.format(sect)) - for k,v in self.cfg.items(sect): - logger.info(' {} = {}'.format(k,v)) - + try: + logger.info("Config file content:") + for sect in self.cfg.sections(): + logger.info(' Section: {}'.format(sect)) + for k,v in self.cfg.items(sect): + logger.info(' {} = {}'.format(k,v)) + except: + pass self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) From 225b12190445c568950149a5c6731c2d442bd741 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 20:58:28 +0000 Subject: [PATCH 013/262] renamed T_livetime to tau_tritium for better readability --- mermithid/misc/SensitivityFormulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index adda8b63..1961deb7 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -124,7 +124,7 @@ def __init__(self, config_path): self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - self.T_livetime = tritium_livetime + self.tau_tritium = tritium_livetime if self.Experiment.atomic: self.T_mass = tritium_mass_atomic self.Te_crosssection = tritium_electron_crosssection_atomic @@ -146,7 +146,7 @@ def __init__(self, config_path): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.T_livetime + signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.tau_livetime if not self.Experiment.atomic: signal_rate *= 2 return signal_rate From 41bfd43f9f5721e61f1d9697e3c91eb80c384fb7 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:05:19 +0000 Subject: [PATCH 014/262] added function for SignalEvents and BackgroundEvents --- mermithid/misc/SensitivityFormulas.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 1961deb7..70dfc126 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -151,6 +151,14 @@ def SignalRate(self): signal_rate *= 2 return signal_rate + def SignalEvents(self): + """Number of signal events.""" + return self.SignalRate()*self.Experiment.LiveTime*self.DeltaEWidth()**3 + + def BackgroundEvents(self): + """Number of background events.""" + return self.Experiment.background_rate_per_eV*self.Experiment.LiveTime*self.DeltaEWidth() + def DeltaEWidth(self): """optimal energy bin width""" labels, sigmas, deltas = self.get_systematics() @@ -161,7 +169,7 @@ def StatSens(self): """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" sig_rate = self.SignalRate() DeltaE = self.DeltaEWidth() - sens = 4/(6*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE +self.Experiment.background_rate_per_eV*self.Experiment.LiveTime/DeltaE) return sens From 5ceb9cc06fcd8b5d444bef4418659c0eeeb011f7 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:21:37 +0000 Subject: [PATCH 015/262] Added function to calculate sterial m^2 limit and conversion functions from Ue4^2 to sin^2(2theta) and vice versa --- mermithid/misc/SensitivityFormulas.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 70dfc126..70f7ca73 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -96,7 +96,11 @@ def track_length(rho, kin_energy=None, molecular=True): crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic return 1 / (rho * crosssect * beta(kin_energy) * c0) +def sin2theta_sq_to_Ue4_sq(sin2theta_sq): + return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) +def Ue4_sq_to_sin2theta_sq(Ue4_sq): + return 4*Ue4_sq*(1-Ue4_sq) ############################################################################### class Sensitivity(object): @@ -201,6 +205,9 @@ def CL90(self, **kwargs): # 90% of gaussian are contained in +-1.64 sigma region return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + def sterial_m2_limit(self, Ue4_sq): + return np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()) + # PHYSICS Functions def BToKeErr(self, BErr, B): return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) From e619510378d1f135f19646572ba2afeb9257e34e Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:30:44 +0000 Subject: [PATCH 016/262] added comments to explain meaning of sigma and delta --- mermithid/misc/SensitivityFormulas.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 70f7ca73..cf20ce1f 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -218,6 +218,18 @@ def track_length(self, rho): # SYSTEMATICS def get_systematics(self): + """ Returns list of energy broadenings (sigmas) and + uncertainties on these energy broadenings (deltas) + for all considered systematics. We need to make sure + that we do not include effects twice or miss any + important effect. + + Returns: + * list of labels + * list of energy broadenings + * list of energy broadening uncertainties + """ + # Different types of uncertainty contributions sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() sigma_f, delta_sigma_f = self.syst_frequency_extraction() From e6b78f5a35ba1fe815fb5f96c4a282a51efeac88 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:32:18 +0000 Subject: [PATCH 017/262] Added NotImplementedError for systematics that are not implemented --- mermithid/misc/SensitivityFormulas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index cf20ce1f..0fe75617 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -398,6 +398,8 @@ def syst_missing_tracks(self): sigma = self.MissingTracks.Default_Systematic_Smearing delta = self.MissingTracks.Default_Systematic_Uncertainty return sigma, delta + else: + raise NotImplementedError("Missing track systematic is not implemented.") def syst_plasma_effects(self): if self.PlasmaEffects.UseFixedValue: @@ -405,4 +407,4 @@ def syst_plasma_effects(self): delta = self.PlasmaEffects.Default_Systematic_Uncertainty return sigma, delta else: - raise NotImplementedError() + raise NotImplementedError("Plasma effect sysstematic is not implemented.") From 4f51bc88bd8eb8f4763b6bd71ee4ba2c715495fc Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:42:20 +0000 Subject: [PATCH 018/262] added comments to magnetic field uncertainties to explain what each effect should contain or is meant to contain --- mermithid/misc/SensitivityFormulas.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 0fe75617..867c86a0 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -335,9 +335,25 @@ def syst_frequency_extraction(self): sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) + # the magnetic_field_smearing and uncertainty added here consider the following effect: + # thinking in terms of a phase II track, there is some smearing of the track / width of the track which influences the frequency extraction + # this does not account for any effect comming from converting frequency to energy + # the reason behind the track width / smearing is the change in B field that the electron sees within one axial oscillation. + # Depending on the trap shape this smearing may be different. + return sigma_f, delta_sigma_f def syst_magnetic_field(self): + + # magnetic field uncertainties can be decomposed in several part + # * true magnetic field inhomogeneity + # (would be there also without a trap) + # * magnetic field calibration has uncertainties + # (would be there also without a trap) + # * position / pitch angle reconstruction has uncertainties + # (this can even be the degenerancy we see for harmonic traps) + # (depends on trap shape) + if self.MagneticField.UseFixedValue: sigma = self.MagneticField.Default_Systematic_Smearing delta = self.MagneticField.Default_Systematic_Uncertainty @@ -363,6 +379,8 @@ def syst_magnetic_field(self): BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) + # position uncertainty is linear in wavelength + # position uncertainty is nearly constant w.r.t. radial position rRecoErr = self.MagneticField.rRecoErr delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr From 6ea225827d7b2f85140b4b5d1dc52fd4d636d393 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 21:46:38 +0000 Subject: [PATCH 019/262] Made a BackgroundRate function. This allows us to combine the background in several parts --- mermithid/misc/SensitivityFormulas.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 867c86a0..c93c84f6 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -155,18 +155,23 @@ def SignalRate(self): signal_rate *= 2 return signal_rate + def BackgroundRate(self): + """background rate, can be calculated from multiple components. + Assumes that background rate is constant over considered energy / frequency range.""" + return self.Experiment.background_rate_per_eV + def SignalEvents(self): """Number of signal events.""" return self.SignalRate()*self.Experiment.LiveTime*self.DeltaEWidth()**3 def BackgroundEvents(self): """Number of background events.""" - return self.Experiment.background_rate_per_eV*self.Experiment.LiveTime*self.DeltaEWidth() + return self.BackgroundRate()*self.Experiment.LiveTime*self.DeltaEWidth() def DeltaEWidth(self): """optimal energy bin width""" labels, sigmas, deltas = self.get_systematics() - return np.sqrt(self.Experiment.background_rate_per_eV/self.SignalRate() + return np.sqrt(self.BackgroundRate()/self.SignalRate() + 8*np.log(2)*(np.sum(sigmas**2))) def StatSens(self): @@ -174,7 +179,7 @@ def StatSens(self): sig_rate = self.SignalRate() DeltaE = self.DeltaEWidth() sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - +self.Experiment.background_rate_per_eV*self.Experiment.LiveTime/DeltaE) + +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) return sens def SystSens(self): From 20ce8b3925e27aec2d8fade7375689f34e600b4f Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 16 Nov 2021 22:09:29 +0000 Subject: [PATCH 020/262] Added link to justify comment --- mermithid/misc/SensitivityFormulas.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index c93c84f6..e976d62f 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -386,6 +386,7 @@ def syst_magnetic_field(self): # position uncertainty is linear in wavelength # position uncertainty is nearly constant w.r.t. radial position + # based on https://3.basecamp.com/3700981/buckets/3107037/uploads/3442593126 rRecoErr = self.MagneticField.rRecoErr delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr From 0d6aa32545828b29d5f47bb2a0d9ed8966b75fa5 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Mon, 29 Nov 2021 15:26:39 +0000 Subject: [PATCH 021/262] added comment to describe meaning of syst_missing_tracks --- mermithid/misc/SensitivityFormulas.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index e976d62f..c4411b6c 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -418,6 +418,10 @@ def syst_magnetic_field(self): return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) def syst_missing_tracks(self): + # this systematic should describe the energy broadening due to the line shape. + # Line shape is caused because you miss the first n tracks but then detect the n+1 + # track and you assume that this is the start frequency. + # This depends on the gas composition, density and cross-section. if self.MissingTracks.UseFixedValue: sigma = self.MissingTracks.Default_Systematic_Smearing delta = self.MissingTracks.Default_Systematic_Uncertainty From eff21a20d827a151ad6b3df174e6ba9e55f1f0a5 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Fri, 10 Dec 2021 14:02:07 +0100 Subject: [PATCH 022/262] Fixing variable name. I wanted to change the variable before but did not do it consistently in all places. --- mermithid/misc/SensitivityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index c4411b6c..62200b21 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -150,7 +150,7 @@ def __init__(self, config_path): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.tau_livetime + signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: signal_rate *= 2 return signal_rate From d7ed45dc1da0015629fd2cb280fef9c84511e725 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Fri, 10 Dec 2021 14:18:06 +0100 Subject: [PATCH 023/262] fixing steril sensitivity formula There as a square missing on the systematic part. In addition it calculated the sensitivity sigma but not the limit. We now calculate the limit just in the way we do it for the active masses. --- mermithid/misc/SensitivityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 62200b21..c32f33f1 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -211,7 +211,7 @@ def CL90(self, **kwargs): return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()) + return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # PHYSICS Functions def BToKeErr(self, BErr, B): From 4be012e0f3b3fa21cc0b62109dba4973448907ce Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 12 Dec 2021 14:37:12 -0800 Subject: [PATCH 024/262] small fixes to get test script working --- mermithid/processors/misc/SensitivityCurveProcessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/misc/SensitivityCurveProcessor.py index 309f4d42..bd5acece 100644 --- a/mermithid/processors/misc/SensitivityCurveProcessor.py +++ b/mermithid/processors/misc/SensitivityCurveProcessor.py @@ -169,11 +169,11 @@ def InternalRun(self): logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.Experiment.v_eff)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ - self.sens_main.T_livetime*2)) + self.sens_main.tau_tritium*2)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ - self.sens_main.T_livetime*2)) + self.sens_main.tau_tritium*2)) return True From 100b754566ea92b565143444bb16d72f46e82b03 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 12 Dec 2021 18:51:18 -0800 Subject: [PATCH 025/262] only calculate gas fraction if in name space --- mermithid/misc/SensitivityFormulas.py | 31 +++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 56a6cc8a..3f8c56c6 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -3,7 +3,7 @@ Author: R. Reimann, C. Claessens Date:11/17/2020 -The statistical method and formulars are described in +The statistical method and formulars are described in CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np @@ -152,12 +152,15 @@ def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: - avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) - signal_rate *= avg_n_T_atoms + if hasattr(self.Experiment, 'gas_fractions'): + avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) + signal_rate *= avg_n_T_atoms + else: + signal_rate *= 2 return signal_rate def BackgroundRate(self): - """background rate, can be calculated from multiple components. + """background rate, can be calculated from multiple components. Assumes that background rate is constant over considered energy / frequency range.""" return self.Experiment.background_rate_per_eV @@ -190,7 +193,7 @@ def SystSens(self): return sens def sensitivity(self, **kwargs): - """Combined statisical and systematic uncertainty. + """Combined statisical and systematic uncertainty. Using kwargs settings in namespaces can be changed. Example how to change number density which lives in namespace Experiment: self.sensitivity(Experiment={"number_density": rho}) @@ -215,7 +218,7 @@ def sterial_m2_limit(self, Ue4_sq): return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # PHYSICS Functions - + def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas_fractions): """ Given gas composition info (H2 vs. other gases, and how much of each H2-type isotopolog), returns an average number of tritium atoms per gas particle. @@ -243,11 +246,11 @@ def track_length(self, rho): # SYSTEMATICS def get_systematics(self): - """ Returns list of energy broadenings (sigmas) and + """ Returns list of energy broadenings (sigmas) and uncertainties on these energy broadenings (deltas) - for all considered systematics. We need to make sure - that we do not include effects twice or miss any - important effect. + for all considered systematics. We need to make sure + that we do not include effects twice or miss any + important effect. Returns: * list of labels @@ -363,7 +366,7 @@ def syst_frequency_extraction(self): # the magnetic_field_smearing and uncertainty added here consider the following effect: # thinking in terms of a phase II track, there is some smearing of the track / width of the track which influences the frequency extraction # this does not account for any effect comming from converting frequency to energy - # the reason behind the track width / smearing is the change in B field that the electron sees within one axial oscillation. + # the reason behind the track width / smearing is the change in B field that the electron sees within one axial oscillation. # Depending on the trap shape this smearing may be different. return sigma_f, delta_sigma_f @@ -371,7 +374,7 @@ def syst_frequency_extraction(self): def syst_magnetic_field(self): # magnetic field uncertainties can be decomposed in several part - # * true magnetic field inhomogeneity + # * true magnetic field inhomogeneity # (would be there also without a trap) # * magnetic field calibration has uncertainties # (would be there also without a trap) @@ -439,8 +442,8 @@ def syst_magnetic_field(self): def syst_missing_tracks(self): # this systematic should describe the energy broadening due to the line shape. - # Line shape is caused because you miss the first n tracks but then detect the n+1 - # track and you assume that this is the start frequency. + # Line shape is caused because you miss the first n tracks but then detect the n+1 + # track and you assume that this is the start frequency. # This depends on the gas composition, density and cross-section. if self.MissingTracks.UseFixedValue: sigma = self.MissingTracks.Default_Systematic_Smearing From 10a906c2f221a2835f93e8e8c6f2fb88eaeb5481 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 12 Dec 2021 19:14:22 -0800 Subject: [PATCH 026/262] moved sensitivity curve processor to new location and added sensitivity test script --- .../SensitivityCurveProcessor.py | 0 mermithid/processors/Sensitivity/__init__.py | 6 ++ mermithid/processors/__init__.py | 1 + mermithid/processors/misc/__init__.py | 1 - tests/Misc_test.py | 27 --------- tests/Sensitivity_test.py | 56 +++++++++++++++++++ 6 files changed, 63 insertions(+), 28 deletions(-) rename mermithid/processors/{misc => Sensitivity}/SensitivityCurveProcessor.py (100%) create mode 100644 mermithid/processors/Sensitivity/__init__.py create mode 100644 tests/Sensitivity_test.py diff --git a/mermithid/processors/misc/SensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py similarity index 100% rename from mermithid/processors/misc/SensitivityCurveProcessor.py rename to mermithid/processors/Sensitivity/SensitivityCurveProcessor.py diff --git a/mermithid/processors/Sensitivity/__init__.py b/mermithid/processors/Sensitivity/__init__.py new file mode 100644 index 00000000..2452a4e2 --- /dev/null +++ b/mermithid/processors/Sensitivity/__init__.py @@ -0,0 +1,6 @@ +''' +''' + +from __future__ import absolute_import + +from .SensitivityCurveProcessor import SensitivityCurveProcessor diff --git a/mermithid/processors/__init__.py b/mermithid/processors/__init__.py index bc325afd..d7083508 100644 --- a/mermithid/processors/__init__.py +++ b/mermithid/processors/__init__.py @@ -8,3 +8,4 @@ from . import plots from . import misc from . import Fitters +from . import Sensitivity diff --git a/mermithid/processors/misc/__init__.py b/mermithid/processors/misc/__init__.py index 527b32d1..3c9983d2 100644 --- a/mermithid/processors/misc/__init__.py +++ b/mermithid/processors/misc/__init__.py @@ -5,4 +5,3 @@ from .FrequencyEnergyConversionProcessor import FrequencyEnergyConversionProcessor from .FrequencyShifter import FrequencyShifter -from .SensitivityCurveProcessor import SensitivityCurveProcessor diff --git a/tests/Misc_test.py b/tests/Misc_test.py index 377f555b..33bd7c73 100644 --- a/tests/Misc_test.py +++ b/tests/Misc_test.py @@ -11,8 +11,6 @@ from morpho.utilities import morphologging, parser logger = morphologging.getLogger(__name__) -import matplotlib.pyplot as plt -import numpy as np class MiscTest(unittest.TestCase): @@ -38,31 +36,6 @@ def test_FreqConversionTest(self): logger.info("Resulting energies: %s"%freq_proc.energies) - def test_SensitivityCurveProcessor(self): - from mermithid.processors.misc import SensitivityCurveProcessor - - - sens_config_dict = { - # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", - "plot_path": "./sensitivity_curve.pdf", - # optional - "track_length_axis": True, - "molecular_axis": True, - "atomic_axis": False, - "y_limits": [1e-2, 1e2], - "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", - "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, - "comparison_curve": False, - "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", - "B": np.arange(1, 8)*1e-6 - - } - sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() - - if __name__ == '__main__': diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py new file mode 100644 index 00000000..e16b4fa9 --- /dev/null +++ b/tests/Sensitivity_test.py @@ -0,0 +1,56 @@ +""" +Script to test the Sensitivty processors +Author: C. Claessens +Date: December 12, 2021 +""" + +import unittest + +from morpho.utilities import morphologging, parser +logger = morphologging.getLogger(__name__) + +import numpy as np + +class SensitivityTest(unittest.TestCase): + + def test_SensitivityCurveProcessor(self): + from mermithid.processors.Sensitivity import SensitivityCurveProcessor + + + sens_config_dict = { + # required + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", + "plot_path": "./sensitivity_curve.pdf", + # optional + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": False, + "y_limits": [1e-2, 1e2], + "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", + "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, + "comparison_curve": False, + "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", + "B": np.arange(1, 8)*1e-6 + + } + sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() + + + +if __name__ == '__main__': + + args = parser.parse_args(False) + + + logger = morphologging.getLogger('morpho', + level=args.verbosity, + stderr_lb=args.stderr_verbosity, + propagate=False) + logger = morphologging.getLogger(__name__, + level=args.verbosity, + stderr_lb=args.stderr_verbosity, + propagate=False) + + unittest.main() From 9111d983c2d285daac8978cc44f4eafe0967453f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 16 Dec 2021 10:48:24 -0800 Subject: [PATCH 027/262] copied sensitivity formulas in new processor --- .../AnalyticSensitivityEstimation.py | 121 ++++++++++++++++++ mermithid/processors/Sensitivity/__init__.py | 1 + tests/Sensitivity_test.py | 18 +++ 3 files changed, 140 insertions(+) create mode 100644 mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py new file mode 100644 index 00000000..3a63ff4f --- /dev/null +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -0,0 +1,121 @@ +''' +Calculate analytic sensitivity +function. +Author: C. Claessens +Date:12/16/2021 + +More description +''' + +from __future__ import absolute_import + + +import numpy as np + + +# Numericalunits is a package to handle units and some natural constants +# natural constants + +from numericalunits import meV, eV + + +# morpho imports +from morpho.utilities import morphologging, reader +from morpho.processors import BaseProcessor +from mermithid.misc.SensitivityFormulas import Sensitivity + + +logger = morphologging.getLogger(__name__) + + + +__all__ = [] +__all__.append(__name__) + +class AnalyticSensitivityEstimation(BaseProcessor, Sensitivity): + ''' + Description + Args: + + Inputs: + + Output: + + ''' + # first do the BaseProcessor __init__ + def __init__(self, name, *args, **kwargs): + BaseProcessor.__init__(self, name, *args, **kwargs) + + def InternalConfigure(self, params): + ''' + Configure + ''' + # file paths + self.config_file_path = reader.read_param(params, 'config_file_path', "required") + + # setup sensitivities requires to run Sensitivity input class __init__ + Sensitivity.__init__(self, self.config_file_path) + + return True + + + + def InternalRun(self): + + self.results = {'CL90_limit': self.CL90()/eV, 'm_beta_squared_uncertainty': self.sensitivity()/(eV**2)} + + return True + + + # Sensitivity formulas + + def StatSens(self): + """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + sig_rate = self.SignalRate() + DeltaE = self.DeltaEWidth() + sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) + return sens + + def SystSens(self): + """Pure systematic componenet to sensitivity""" + labels, sigmas, deltas = self.get_systematics() + sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + return sens + + def sensitivity(self, **kwargs): + """Combined statisical and systematic uncertainty. + Using kwargs settings in namespaces can be changed. + Example how to change number density which lives in namespace Experiment: + self.sensitivity(Experiment={"number_density": rho}) + """ + for sect, options in kwargs.items(): + for opt, val in options.items(): + self.__dict__[sect].__dict__[opt] = val + + StatSens = self.StatSens() + SystSens = self.SystSens() + + # Standard deviation on a measurement of m_beta**2 + sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + return sigma_m_beta_2 + + def CL90(self, **kwargs): + """ Gives 90% CL upper limit on neutrino mass.""" + # 90% of gaussian are contained in +-1.64 sigma region + return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + + def sterial_m2_limit(self, Ue4_sq): + return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + + + # print functions + def print_statistics(self): + print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + + def print_systematics(self): + labels, sigmas, deltas = self.get_systematics() + + print() + for label, sigma, delta in zip(labels, sigmas, deltas): + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file diff --git a/mermithid/processors/Sensitivity/__init__.py b/mermithid/processors/Sensitivity/__init__.py index 2452a4e2..036482bf 100644 --- a/mermithid/processors/Sensitivity/__init__.py +++ b/mermithid/processors/Sensitivity/__init__.py @@ -4,3 +4,4 @@ from __future__ import absolute_import from .SensitivityCurveProcessor import SensitivityCurveProcessor +from .AnalyticSensitivityEstimation import AnalyticSensitivityEstimation diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index e16b4fa9..d01eda3a 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -37,6 +37,24 @@ def test_SensitivityCurveProcessor(self): sens_curve.Configure(sens_config_dict) sens_curve.Run() + def test_SensitivityProcessor(self): + from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation + + + sens_config_dict = { + # required + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg" + } + sens = AnalyticSensitivityEstimation("sensitivity_processor") + sens.Configure(sens_config_dict) + sens.Run() + + sens.print_statistics() + sens.print_systematics() + + results = sens.results + logger.info(results) + if __name__ == '__main__': From a9b65d817383d0f584c2cacee489a39c4b6d5b28 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 16 Dec 2021 11:01:53 -0800 Subject: [PATCH 028/262] edited some comments --- .../processors/Sensitivity/AnalyticSensitivityEstimation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py index 3a63ff4f..dd44bcc3 100644 --- a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -53,7 +53,7 @@ def InternalConfigure(self, params): # file paths self.config_file_path = reader.read_param(params, 'config_file_path', "required") - # setup sensitivities requires to run Sensitivity input class __init__ + # Configuration is done in __init__ of SensitivityClass Sensitivity.__init__(self, self.config_file_path) return True @@ -67,7 +67,9 @@ def InternalRun(self): return True - # Sensitivity formulas + # Sensitivity formulas: + # These are the functions that have so far been in the SensitivityClass but would not be used by a fake data experiment + # I did not include DeltaEWidth here becuase I consider it more general information about an experiment that could be used by the fake data studies too def StatSens(self): """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" From a866dc058eea55d2f42f70cebc39f255af8df3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Reimann?= Date: Wed, 26 Jan 2022 11:24:27 +0100 Subject: [PATCH 029/262] Correcting sin_sq_2Theta to Ue4_sq conversion The sin2Theta_sq is already squared and does not need the extra square --- mermithid/misc/SensitivityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 3f8c56c6..1136b2a6 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -97,7 +97,7 @@ def track_length(rho, kin_energy=None, molecular=True): return 1 / (rho * crosssect * beta(kin_energy) * c0) def sin2theta_sq_to_Ue4_sq(sin2theta_sq): - return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) + return 0.5*(1-np.sqrt(1-sin2theta_sq)) def Ue4_sq_to_sin2theta_sq(Ue4_sq): return 4*Ue4_sq*(1-Ue4_sq) From b6fe42c922c5cbce94280a1085e7c5ed89f8046a Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 6 Mar 2022 17:11:09 -0800 Subject: [PATCH 030/262] Made sensitivity vs. density more configurabl. Added processor to make optimization vs. volume plots. --- mermithid/misc/SensitivityFormulas.py | 9 ++-- .../AnalyticSensitivityEstimation.py | 2 +- .../Sensitivity/SensitivityCurveProcessor.py | 48 ++++++++++++------- mermithid/processors/Sensitivity/__init__.py | 1 + tests/Sensitivity_test.py | 43 +++++++++++++---- 5 files changed, 72 insertions(+), 31 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 3f8c56c6..6ef11db0 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -240,6 +240,9 @@ def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas def BToKeErr(self, BErr, B): return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) + def KeToBerr(self, KeErr, B): + return KeErr/e*(2*np.pi*frequency(self.T_endpoint, B)/c0**2) + def track_length(self, rho): return track_length(rho, self.T_endpoint, not self.Experiment.atomic) @@ -265,7 +268,7 @@ def get_systematics(self): sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() - labels = ["Termal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] + labels = ["Thermal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] @@ -274,10 +277,10 @@ def get_systematics(self): sigmas.append(ground_state_width) deltas.append(ground_state_width_uncertainty) - return np.array(labels), np.array(sigmas), np.array(deltas) + return labels, np.array(sigmas), np.array(deltas) def print_statistics(self): - print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + print("Statistical", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py index dd44bcc3..7d5011c8 100644 --- a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -16,7 +16,7 @@ # Numericalunits is a package to handle units and some natural constants # natural constants -from numericalunits import meV, eV +from numericalunits import meV, eV, T # morpho imports diff --git a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py index d7246769..01c086b1 100644 --- a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py @@ -65,8 +65,8 @@ def InternalConfigure(self, params): # options self.comparison_curve = reader.read_param(params, 'comparison_curve', False) - self.B = reader.read_param(params, 'B', 7e-6) - self.B_uncertainty = reader.read_param(params, 'B_uncertainty', 0.05) + self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) + self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) # plot configurations @@ -76,10 +76,13 @@ def InternalConfigure(self, params): self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) + self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) + self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) # goals - self.goals = reader.read_param(params, "goals", {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}) + self.goals = reader.read_param(params, "goals", {}) # setup sensitivities @@ -136,24 +139,25 @@ def InternalRun(self): self.add_goal(value*eV, key) # if B is list plot line for each B - if isinstance(self.B, list) or isinstance(self.B, np.ndarray): - N = len(self.B) - for a, color in self.range(1, N): - sig = self.sens_main.BToKeErr(self.B[a]*T, self.sens_main.MagneticField.nominal_field) + print(self.B_error) + if isinstance(self.B_error, list) or isinstance(self.B_error, np.ndarray): + N = len(self.B_error) + for a, color in self.range(0, N): + sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) self.sens_main.MagneticField.usefixedvalue = True self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = self.B_uncertainty*sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig self.add_sens_line(self.sens_main, color=color) - self.add_text(5e19, 5, self.main_curve_upper_label) - self.add_text(5e19, 2.3, self.main_curve_lower_label) + self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label) + self.add_text(self.label_x_position, self.lower_label_y_position, self.main_curve_lower_label) else: - sig = self.sens_main.BToKeErr(self.B*T, self.sens_main.MagneticField.nominal_field) + sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) self.sens_main.MagneticField.usefixedvalue = True self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = self.B_uncertainty*sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig self.add_sens_line(self.sens_main, color='blue') - self.add_text(5e19, 5, self.main_curve_upper_label) + self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label) # save plot @@ -164,8 +168,10 @@ def InternalRun(self): self.opt_ref = np.argmin(limit) rho_opt = self.rhos[self.opt_ref] + + logger.info('Main curve (veff = {} cm**3, rho = {} /m**3):'.format(self.sens_main.Experiment.v_eff/(cm**3), rho_opt*(m**3))) - logger.info('Sensitivity limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.Experiment.v_eff)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.Experiment.v_eff* self.sens_main.Experiment.LiveTime/ @@ -175,6 +181,9 @@ def InternalRun(self): self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) + self.sens_main.print_statistics() + self.sens_main.print_systematics() + return True @@ -186,6 +195,7 @@ def create_plot(self): ax.set_yscale("log") ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) ax.set_ylim(self.ylim) + #ax.grid() if self.atomic_axis and self.molecular_axis: axis_label = r"(atomic / molecular) number density $\rho\, /\, \mathrm{m}^{-3}$" @@ -236,7 +246,7 @@ def add_comparison_curve(self, label, color='k'): logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff)) logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff* self.sens_ref.Experiment.LiveTime/ - self.sens_ref.T_livetime)) + self.sens_ref.tau_tritium)) self.ax.plot(self.rhos*m**3, limit, color=color) self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) @@ -274,10 +284,12 @@ def get_relative(val, axis): def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") - self.ax.text(3e14, 1.1*value/eV, label) + self.ax.text(3e18, 0.8*value/eV, label) def add_sens_line(self, sens, **kwargs): - self.ax.plot(self.rhos*m**3, [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos], **kwargs) + limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.ax.plot(self.rhos*m**3, limits, **kwargs) + logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) def add_text(self, x, y, text): self.ax.text(x, y, text) @@ -296,7 +308,7 @@ def save(self, savepath, **kwargs): } #"Keywords": keywords} if savepath is not None: - self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=200, metadata=metadata) + self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=300, metadata=metadata) self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) diff --git a/mermithid/processors/Sensitivity/__init__.py b/mermithid/processors/Sensitivity/__init__.py index 036482bf..1b2d3e16 100644 --- a/mermithid/processors/Sensitivity/__init__.py +++ b/mermithid/processors/Sensitivity/__init__.py @@ -5,3 +5,4 @@ from .SensitivityCurveProcessor import SensitivityCurveProcessor from .AnalyticSensitivityEstimation import AnalyticSensitivityEstimation +from .ConstantSensitivityParameterPlots import ConstantSensitivityParameterPlots diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index d01eda3a..d841f3b9 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,19 +19,25 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg", + #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "plot_path": "./sensitivity_curve.pdf", # optional "track_length_axis": True, - "molecular_axis": True, - "atomic_axis": False, - "y_limits": [1e-2, 1e2], - "main_curve_upper_label": r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$", - "goals": {"Phase III (2 eV)": 2, "Phase IV (40 meV)": 0.04}, + "molecular_axis": False, + "atomic_axis": True, + "y_limits": [1e-1, 1e2], + "density_range": [1e12,1e21], + "main_curve_upper_label": r"Atomic CRES at 0.04 T"+"\n"+r"$V_\mathrm{eff} = 0.01\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", + "main_curve_lower_label": r"$\sigma_B = 0.15\,\mathrm{ppm}$", + "goals": {"Phase III (0.4 eV)": 0.4},# "Phase IV (40 meV)": 0.04}, "comparison_curve": False, "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", - "B": np.arange(1, 8)*1e-6 - + "B_inhomogeneity": np.arange(0.15, 2.05, 0.15)*1e-6, + "B_inhom_uncertainty": 0.05, + "lower_label_y_position": 0.5, + "upper_label_y_position": 5, + "label_x_position": 1e14 } sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -43,7 +49,8 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_FSCD_molecular_V_eff_2cm3.cfg" + #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } sens = AnalyticSensitivityEstimation("sensitivity_processor") sens.Configure(sens_config_dict) @@ -55,6 +62,24 @@ def test_SensitivityProcessor(self): results = sens.results logger.info(results) +""" def test_ConstantSensitivityCurvesProcessor(self): + from mermithid.processors.Sensitivity import ConstantSensitivityParameterPlots + + + sens_config_dict = { + # required + #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + } + sens = ConstantSensitivityParameterPlots("sensitivity_processor") + sens.Configure(sens_config_dict) + sens.Run() + + sens.print_statistics() + sens.print_systematics()""" + + + if __name__ == '__main__': From 2d45e92a7b6d160e640eaf1e505f2bc49af089bf Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 6 Mar 2022 17:17:28 -0800 Subject: [PATCH 031/262] forgot to add optimization processor --- .../ConstantSensitivityParameterPlots.py | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py new file mode 100644 index 00000000..1b4e1275 --- /dev/null +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -0,0 +1,298 @@ +''' +Calculate analytic sensitivity +function. +Author: C. Claessens +Date:12/16/2021 + +More description +''' + +from __future__ import absolute_import + +import matplotlib.pyplot as plt +import numpy as np +from scipy.optimize import minimize +import time +import warnings +warnings.filterwarnings("ignore") + +# Numericalunits is a package to handle units and some natural constants +# natural constants + +from numericalunits import meV, eV, m, T + + +# morpho imports +from morpho.utilities import morphologging, reader +from morpho.processors import BaseProcessor +from mermithid.misc.SensitivityFormulas import Sensitivity + + +logger = morphologging.getLogger(__name__) + + + +__all__ = [] +__all__.append(__name__) + +class ConstantSensitivityParameterPlots(BaseProcessor, Sensitivity): + ''' + Description + Args: + + Inputs: + + Output: + + ''' + # first do the BaseProcessor __init__ + def __init__(self, name, *args, **kwargs): + BaseProcessor.__init__(self, name, *args, **kwargs) + + def InternalConfigure(self, params): + ''' + Configure + ''' + # file paths + self.config_file_path = reader.read_param(params, 'config_file_path', "required") + #self.x_parameters = reader.read_param(params, 'x_parameters', ['v_eff']) + #self.y_parameters = reader.read_param(params, 'y_parameters', ['']) + self.sensitivity_target = reader.read_param(params, 'sensitivity_target', [0.4**2/np.sqrt(1.64), 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] ) + self.initialInhomogeneity = reader.read_param(params, 'initial_Inhomogeneity', 1e-8) + + self.veff_range = reader.read_param(params, 'veff_range', [0.001, 1]) + self.density_range = reader.read_param(params, 'density_range', [1.e16,1.e19]) + self.BError_range = reader.read_param(params, 'BError_range', [1e-8,1e-3]) + + # Configuration is done in __init__ of SensitivityClass + Sensitivity.__init__(self, self.config_file_path) + + self.rhos = np.linspace(self.density_range[0], self.density_range[1], 5000)/(m**3) + self.veffs = np.logspace(np.log10(self.veff_range[0]), np.log10(self.veff_range[1]), 25)*m**3 + self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 2000) + + return True + + + + def InternalRun(self): + + + + #self.results = {'CL90_limit': self.CL90()/eV, 'm_beta_squared_uncertainty': self.sensitivity()/(eV**2)} + #print(self.results) + + self.needed_Bs = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.needed_res = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.rho_opts = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.CLs = np.empty((len(self.sensitivity_target), len(self.veffs))) + index = [] + + for j in range(len(self.sensitivity_target)): + for i, veff in enumerate(self.veffs): + print('\nVeff = {} m³'.format(veff/(m**3))) + self.Experiment.v_eff = veff + self.MagneticField.inhomogenarity = self.initialInhomogeneity + self.rho_opts[j, i]=self.FindOptimumPressure() + n = 0 + drho = self.density_range[1]/(m**3)*1.5 + + while np.abs(drho) > 1.5*np.diff(self.rhos)[0] and n<10: + print('Iteration: {}'.format(n)) + n+=1 + self.needed_Bs[j, i], self.needed_res[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + new_rho = self.FindOptimumPressure() + drho = self.rho_opts[j, i]-new_rho + self.rho_opts[j, i] = new_rho + self.needed_Bs[j, i], self.needed_res[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + + self.CLs[j, i] = self.CL90() + + + + index.append(np.where((self.CLs[j]/eVself.Berrors[1]))) + print('Achieved 90CL limits: {}'.format(self.CLs[j][index[j]]/eV)) + time.sleep(2) + + + plt.figure(figsize=(15, 5)) + + # plt.subplot(221) + # plt.plot(self.veffs[index]/(m**3), self.CLs[index]/eV) + # #plt.colorbar() + # plt.xlabel('Effective Volume (m³)') + # plt.ylabel('90% CL (eV)') + # plt.xscale('log') + # plt.yscale('log') + # plt.tight_layout() + + + plt.subplot(131) + + for j in range(len(self.sensitivity_target)): + plt.plot(self.veffs[index[j]]/(m**3), self.needed_Bs[j][index[j]], label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + #plt.scatter(self.veffs/(m**3), self.needed_Bs, c=self.CLs/eV, marker='.') + #plt.colorbar() + plt.xlabel('Effective Volume (m³)') + plt.ylabel('Required field homogeneity') + plt.xscale('log') + plt.yscale('log') + plt.legend() + plt.tight_layout() + #plt.savefig('B_vs_veff.pdf') + + plt.subplot(132) + for j in range(len(self.sensitivity_target)): + plt.plot(self.veffs[index[j]]/(m**3), self.needed_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.xlabel('Effective Volume (m³)') + plt.ylabel('Total energy resolution (eV)') + plt.xscale('log') + #plt.yscale('log') + plt.legend() + plt.tight_layout() + #plt.savefig('res_vs_veff.pdf') + + plt.subplot(133) + for j in range(len(self.sensitivity_target)): + plt.plot(self.veffs[index[j]]/(m**3), self.rho_opts[j][index[j]]*m**3, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.xlabel('Effective Volume (m³)') + plt.ylabel('Optimum number density (1/m³)') + plt.xscale('log') + #plt.yscale('log') + plt.legend() + plt.tight_layout() + + plt.savefig('B_res_rho_vs_veff.pdf') + plt.savefig('B_res_rho_vs_veff.png') + plt.show() + return True + + def SensVSrho(self, rho): + self.Experiment.number_density = rho + return self.CL90(Experiment={"number_density": rho}) + + def DistanceToTargetVSBError(self, BError, sensitivity_target): + sig = self.BToKeErr(self.MagneticField.nominal_field*BError, self.MagneticField.nominal_field) + self.MagneticField.usefixedvalue = True + self.MagneticField.default_systematic_smearing = sig + self.MagneticField.default_systematic_uncertainty = 0.05*sig + #print(self.sensitivity()/(eV**2), self.sensitivity_target) + return np.abs(self.sensitivity()/(eV**2)-sensitivity_target) + + def FindOptimumPressure(self): + + + limit = [self.SensVSrho(rho)/eV for rho in self.rhos] + opt_rho_index = np.argmin(limit) + + rho_opt = self.rhos[opt_rho_index] + if rho_opt == self.rhos[0] or rho_opt == self.rhos[-1]: + raise ValueError('Optimum rho {} is on edge or range'.format(rho_opt*m**3)) + + result = minimize(self.SensVSrho, rho_opt) + if result.success: + rho_opt = result.x[0] + + limit = self.CL90(Experiment={"number_density": rho_opt})/eV + print('\tOptimum density: {}'.format(rho_opt*m**3)) + return rho_opt + + def FindMaxAllowedBerror(self, sensitivity_target): + distance_to_target = [self.DistanceToTargetVSBError(Berror, sensitivity_target) for Berror in self.Berrors] + optBerror_index = np.argmin(distance_to_target) + Berror_opt = self.Berrors[optBerror_index] + + + + #result = minimize(self.DistanceToTargetVSBError, Berror_opt, args=(sensitivity_target)) + #if result.success and result.x[0]>0: + # Berror_opt = result.x[0] + + sig = self.BToKeErr(self.MagneticField.nominal_field*Berror_opt, self.MagneticField.nominal_field) + self.MagneticField.usefixedvalue = True + self.MagneticField.default_systematic_smearing = sig + self.MagneticField.default_systematic_uncertainty = 0.05*sig + + print('\tAllowed B relative error: {}'.format(Berror_opt)) + return Berror_opt + + + def BHomogeneityAndResNeeded(self, sensitivity_target): + + #sigma_sys = np.sqrt((self.sensitivity_target*eV**2)**2 - self.StatSens()**2) + #print('Stat sens: {}'.format(self.StatSens()/eV**2)) + + neededBres = self.FindMaxAllowedBerror(sensitivity_target) + labels, sigmas, deltas = self.get_systematics() + #b_sigma_tmp = (sigma_sys/4)**2 + needed_total_sigma = 0 + for i,l in enumerate(labels): + #print('\t', l, sigmas[i]/eV, deltas[i]) + if l != 'Magnetic Field': + #b_sigma_tmp -= (sigmas[i]*deltas[i])**2 + needed_total_sigma += sigmas[i]**2 + else: + sigma_b_old = sigmas[i] + delta_b_old = deltas[i] + + needed_sigma_b = sigma_b_old #np.sqrt(b_sigma_tmp)/delta_b_old + needed_b = self.KeToBerr(needed_sigma_b, self.MagneticField.nominal_field) + needed_total_sigma = np.sqrt(needed_sigma_b**2 + needed_total_sigma) + return needed_b/self.MagneticField.nominal_field, needed_total_sigma + + + + # Sensitivity formulas: + # These are the functions that have so far been in the SensitivityClass but would not be used by a fake data experiment + # I did not include DeltaEWidth here becuase I consider it more general information about an experiment that could be used by the fake data studies too + + def StatSens(self): + """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + sig_rate = self.SignalRate() + DeltaE = self.DeltaEWidth() + sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) + return sens + + def SystSens(self): + """Pure systematic componenet to sensitivity""" + labels, sigmas, deltas = self.get_systematics() + sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + return sens + + def sensitivity(self, **kwargs): + """Combined statisical and systematic uncertainty. + Using kwargs settings in namespaces can be changed. + Example how to change number density which lives in namespace Experiment: + self.sensitivity(Experiment={"number_density": rho}) + """ + for sect, options in kwargs.items(): + for opt, val in options.items(): + self.__dict__[sect].__dict__[opt] = val + + StatSens = self.StatSens() + SystSens = self.SystSens() + + # Standard deviation on a measurement of m_beta**2 + sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + return sigma_m_beta_2 + + def CL90(self, **kwargs): + """ Gives 90% CL upper limit on neutrino mass.""" + # 90% of gaussian are contained in +-1.64 sigma region + return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + + def sterial_m2_limit(self, Ue4_sq): + return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + + + # print functions + def print_statistics(self): + print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + + def print_systematics(self): + labels, sigmas, deltas = self.get_systematics() + + print() + for label, sigma, delta in zip(labels, sigmas, deltas): + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file From 73acfca5de4d3eadafed19368a180f070855357f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 7 Mar 2022 16:45:07 -0800 Subject: [PATCH 032/262] added delta_res plot and got minimization working --- mermithid/misc/SensitivityFormulas.py | 2 +- .../ConstantSensitivityParameterPlots.py | 85 +++++++++++-------- tests/Sensitivity_test.py | 8 +- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/misc/SensitivityFormulas.py index 6ef11db0..1be88932 100644 --- a/mermithid/misc/SensitivityFormulas.py +++ b/mermithid/misc/SensitivityFormulas.py @@ -277,7 +277,7 @@ def get_systematics(self): sigmas.append(ground_state_width) deltas.append(ground_state_width_uncertainty) - return labels, np.array(sigmas), np.array(deltas) + return np.array(labels), np.array(sigmas), np.array(deltas) def print_statistics(self): print("Statistical", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index 1b4e1275..8ce312ec 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -55,8 +55,6 @@ def InternalConfigure(self, params): ''' # file paths self.config_file_path = reader.read_param(params, 'config_file_path', "required") - #self.x_parameters = reader.read_param(params, 'x_parameters', ['v_eff']) - #self.y_parameters = reader.read_param(params, 'y_parameters', ['']) self.sensitivity_target = reader.read_param(params, 'sensitivity_target', [0.4**2/np.sqrt(1.64), 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] ) self.initialInhomogeneity = reader.read_param(params, 'initial_Inhomogeneity', 1e-8) @@ -67,9 +65,9 @@ def InternalConfigure(self, params): # Configuration is done in __init__ of SensitivityClass Sensitivity.__init__(self, self.config_file_path) - self.rhos = np.linspace(self.density_range[0], self.density_range[1], 5000)/(m**3) + self.rhos = np.linspace(self.density_range[0], self.density_range[1], 2000)/(m**3) self.veffs = np.logspace(np.log10(self.veff_range[0]), np.log10(self.veff_range[1]), 25)*m**3 - self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 2000) + self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 1000) return True @@ -84,6 +82,7 @@ def InternalRun(self): self.needed_Bs = np.empty((len(self.sensitivity_target), len(self.veffs))) self.needed_res = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.needed_res_sigma = np.empty((len(self.sensitivity_target), len(self.veffs))) self.rho_opts = np.empty((len(self.sensitivity_target), len(self.veffs))) self.CLs = np.empty((len(self.sensitivity_target), len(self.veffs))) index = [] @@ -100,11 +99,11 @@ def InternalRun(self): while np.abs(drho) > 1.5*np.diff(self.rhos)[0] and n<10: print('Iteration: {}'.format(n)) n+=1 - self.needed_Bs[j, i], self.needed_res[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) new_rho = self.FindOptimumPressure() drho = self.rho_opts[j, i]-new_rho self.rho_opts[j, i] = new_rho - self.needed_Bs[j, i], self.needed_res[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + self.needed_Bs[j, i], self.needed_res[j, i], self.needed_res_sigma[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) self.CLs[j, i] = self.CL90() @@ -115,7 +114,7 @@ def InternalRun(self): time.sleep(2) - plt.figure(figsize=(15, 5)) + plt.figure(figsize=(10, 5)) # plt.subplot(221) # plt.plot(self.veffs[index]/(m**3), self.CLs[index]/eV) @@ -127,7 +126,7 @@ def InternalRun(self): # plt.tight_layout() - plt.subplot(131) + plt.subplot(121) for j in range(len(self.sensitivity_target)): plt.plot(self.veffs[index[j]]/(m**3), self.needed_Bs[j][index[j]], label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) @@ -141,7 +140,22 @@ def InternalRun(self): plt.tight_layout() #plt.savefig('B_vs_veff.pdf') - plt.subplot(132) + plt.subplot(122) + for j in range(len(self.sensitivity_target)): + plt.plot(self.veffs[index[j]]/(m**3), self.rho_opts[j][index[j]]*m**3, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.xlabel('Effective Volume (m³)') + plt.ylabel('Optimum number density (1/m³)') + plt.xscale('log') + #plt.yscale('log') + plt.legend() + plt.tight_layout() + + plt.savefig('B_rho_vs_veff.pdf') + plt.savefig('B_rho_vs_veff.png', dpi=300) + + plt.figure(figsize=(10, 5)) + + plt.subplot(121) for j in range(len(self.sensitivity_target)): plt.plot(self.veffs[index[j]]/(m**3), self.needed_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) plt.xlabel('Effective Volume (m³)') @@ -150,20 +164,20 @@ def InternalRun(self): #plt.yscale('log') plt.legend() plt.tight_layout() - #plt.savefig('res_vs_veff.pdf') - plt.subplot(133) + plt.subplot(122) for j in range(len(self.sensitivity_target)): - plt.plot(self.veffs[index[j]]/(m**3), self.rho_opts[j][index[j]]*m**3, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.plot(self.veffs[index[j]]/(m**3), self.needed_res_sigma[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) plt.xlabel('Effective Volume (m³)') - plt.ylabel('Optimum number density (1/m³)') + plt.ylabel('Uncertainty on total energy resolution (eV)') plt.xscale('log') #plt.yscale('log') plt.legend() plt.tight_layout() + plt.savefig('res_vs_veff.pdf') + plt.savefig('res_vs_veff.png', dpi=300) + - plt.savefig('B_res_rho_vs_veff.pdf') - plt.savefig('B_res_rho_vs_veff.png') plt.show() return True @@ -189,7 +203,7 @@ def FindOptimumPressure(self): if rho_opt == self.rhos[0] or rho_opt == self.rhos[-1]: raise ValueError('Optimum rho {} is on edge or range'.format(rho_opt*m**3)) - result = minimize(self.SensVSrho, rho_opt) + result = minimize(self.SensVSrho, rho_opt, method='Nelder-Mead') if result.success: rho_opt = result.x[0] @@ -204,9 +218,9 @@ def FindMaxAllowedBerror(self, sensitivity_target): - #result = minimize(self.DistanceToTargetVSBError, Berror_opt, args=(sensitivity_target)) - #if result.success and result.x[0]>0: - # Berror_opt = result.x[0] + result = minimize(self.DistanceToTargetVSBError, Berror_opt, args=(sensitivity_target), method='Nelder-Mead') + if result.success and result.x[0]>0: + Berror_opt = result.x[0] sig = self.BToKeErr(self.MagneticField.nominal_field*Berror_opt, self.MagneticField.nominal_field) self.MagneticField.usefixedvalue = True @@ -224,21 +238,24 @@ def BHomogeneityAndResNeeded(self, sensitivity_target): neededBres = self.FindMaxAllowedBerror(sensitivity_target) labels, sigmas, deltas = self.get_systematics() - #b_sigma_tmp = (sigma_sys/4)**2 - needed_total_sigma = 0 - for i,l in enumerate(labels): - #print('\t', l, sigmas[i]/eV, deltas[i]) - if l != 'Magnetic Field': - #b_sigma_tmp -= (sigmas[i]*deltas[i])**2 - needed_total_sigma += sigmas[i]**2 - else: - sigma_b_old = sigmas[i] - delta_b_old = deltas[i] - - needed_sigma_b = sigma_b_old #np.sqrt(b_sigma_tmp)/delta_b_old - needed_b = self.KeToBerr(needed_sigma_b, self.MagneticField.nominal_field) - needed_total_sigma = np.sqrt(needed_sigma_b**2 + needed_total_sigma) - return needed_b/self.MagneticField.nominal_field, needed_total_sigma + # #b_sigma_tmp = (sigma_sys/4)**2 + # needed_total_sigma = 0 + # for i,l in enumerate(labels): + # #print('\t', l, sigmas[i]/eV, deltas[i]) + # if l != 'Magnetic Field': + # #b_sigma_tmp -= (sigmas[i]*deltas[i])**2 + # needed_total_sigma += sigmas[i]**2 + # else: + # sigma_b_old = sigmas[i] + # delta_b_old = deltas[i] + + # needed_sigma_b = sigma_b_old #np.sqrt(b_sigma_tmp)/delta_b_old + needed_sigma_b = sigmas[np.where(labels=='Magnetic Field')] + needed_b_error = self.KeToBerr(needed_sigma_b, self.MagneticField.nominal_field) + # needed_total_sigma = np.sqrt(needed_sigma_b**2 + needed_total_sigma) + needed_total_sigma = np.sqrt(np.sum(sigmas**2)) + needed_total_delta = np.sqrt(np.sum(deltas**2)) + return needed_b_error/self.MagneticField.nominal_field, needed_total_sigma, needed_total_delta diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index d841f3b9..e9a0e68f 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -29,11 +29,11 @@ def test_SensitivityCurveProcessor(self): "y_limits": [1e-1, 1e2], "density_range": [1e12,1e21], "main_curve_upper_label": r"Atomic CRES at 0.04 T"+"\n"+r"$V_\mathrm{eff} = 0.01\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", - "main_curve_lower_label": r"$\sigma_B = 0.15\,\mathrm{ppm}$", + "main_curve_lower_label": r"$\sigma_B = 0.16\,\mathrm{ppm}$", "goals": {"Phase III (0.4 eV)": 0.4},# "Phase IV (40 meV)": 0.04}, "comparison_curve": False, "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", - "B_inhomogeneity": np.arange(0.15, 2.05, 0.15)*1e-6, + "B_inhomogeneity": np.linspace(0.16, 2.0, 10)*1e-6, "B_inhom_uncertainty": 0.05, "lower_label_y_position": 0.5, "upper_label_y_position": 5, @@ -62,7 +62,7 @@ def test_SensitivityProcessor(self): results = sens.results logger.info(results) -""" def test_ConstantSensitivityCurvesProcessor(self): + def test_ConstantSensitivityCurvesProcessor(self): from mermithid.processors.Sensitivity import ConstantSensitivityParameterPlots @@ -76,7 +76,7 @@ def test_SensitivityProcessor(self): sens.Run() sens.print_statistics() - sens.print_systematics()""" + sens.print_systematics() From 425bf138e03ff442b1976efeb93bbed8fe231e60 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 14 Nov 2022 11:34:33 -0800 Subject: [PATCH 033/262] added frequency resolution --- .../ConstantSensitivityParameterPlots.py | 71 +++++++++++++++++-- tests/Sensitivity_test.py | 5 +- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index 8ce312ec..4fdf3f23 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -65,9 +65,35 @@ def InternalConfigure(self, params): # Configuration is done in __init__ of SensitivityClass Sensitivity.__init__(self, self.config_file_path) - self.rhos = np.linspace(self.density_range[0], self.density_range[1], 2000)/(m**3) + self.rhos = np.linspace(self.density_range[0], self.density_range[1], 20)/(m**3) self.veffs = np.logspace(np.log10(self.veff_range[0]), np.log10(self.veff_range[1]), 25)*m**3 - self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 1000) + self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 100) + + freq_res = np.empty(np.shape(self.rhos)) + freq_res_delta = np.empty(np.shape(self.rhos)) + + for i in range(len(self.rhos)): + self.Experiment.number_density = self.rhos[i] + freq_res[i], freq_res_delta[i] = self.syst_frequency_extraction() + + print(freq_res_delta[i]) + + plt.figure() + plt.subplot(121) + plt.plot(self.rhos*m**3, freq_res/eV) + plt.xlabel('Density (/m³') + plt.ylabel('Energy width of frequency resolution (eV)') + plt.xscale('log') + + plt.subplot(122) + plt.plot(self.rhos*m**3, freq_res_delta/eV) + plt.xlabel('Density (/m³)') + plt.ylabel('Uncertainty on energy width of frequency resolution (eV)') + plt.xscale('log') + plt.tight_layout() + + plt.savefig('Frequency_resolution_vs_rho.pdf') + plt.show() return True @@ -83,6 +109,8 @@ def InternalRun(self): self.needed_Bs = np.empty((len(self.sensitivity_target), len(self.veffs))) self.needed_res = np.empty((len(self.sensitivity_target), len(self.veffs))) self.needed_res_sigma = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.needed_freq_res = np.empty((len(self.sensitivity_target), len(self.veffs))) + self.needed_freq_res_sigma = np.empty((len(self.sensitivity_target), len(self.veffs))) self.rho_opts = np.empty((len(self.sensitivity_target), len(self.veffs))) self.CLs = np.empty((len(self.sensitivity_target), len(self.veffs))) index = [] @@ -93,24 +121,26 @@ def InternalRun(self): self.Experiment.v_eff = veff self.MagneticField.inhomogenarity = self.initialInhomogeneity self.rho_opts[j, i]=self.FindOptimumPressure() + self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) n = 0 drho = self.density_range[1]/(m**3)*1.5 - while np.abs(drho) > 1.5*np.diff(self.rhos)[0] and n<10: + while np.abs(drho)> self.rho_opts[j, i] * 0.001 and n<10: print('Iteration: {}'.format(n)) n+=1 - self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + new_rho = self.FindOptimumPressure() drho = self.rho_opts[j, i]-new_rho self.rho_opts[j, i] = new_rho - self.needed_Bs[j, i], self.needed_res[j, i], self.needed_res_sigma[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) + self.needed_Bs[j, i], self.needed_res[j, i], self.needed_res_sigma[j, i], self.needed_freq_res[j, i], self.needed_freq_res_sigma[j, i] = self.BHomogeneityAndResNeeded(self.sensitivity_target[j]) self.CLs[j, i] = self.CL90() index.append(np.where((self.CLs[j]/eVself.Berrors[1]))) - print('Achieved 90CL limits: {}'.format(self.CLs[j][index[j]]/eV)) + print('Achieved 90CL limits: {}'.format(self.CLs[j]/eV)) + print(index[j]) time.sleep(2) @@ -177,6 +207,30 @@ def InternalRun(self): plt.savefig('res_vs_veff.pdf') plt.savefig('res_vs_veff.png', dpi=300) + # plt.figure(figsize=(10, 5)) + + # plt.subplot(121) + # for j in range(len(self.sensitivity_target)): + # plt.plot(self.veffs[index[j]]/(m**3), self.needed_freq_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + # plt.xlabel('Effective Volume (m³)') + # plt.ylabel('Energy width of frequency resolution (eV)') + # plt.xscale('log') + # #plt.yscale('log') + # plt.legend() + # plt.tight_layout() + + # plt.subplot(122) + # for j in range(len(self.sensitivity_target)): + # plt.plot(self.veffs[index[j]]/(m**3), self.needed_freq_res_sigma[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + # plt.xlabel('Effective Volume (m³)') + # plt.ylabel('Uncertainty on energy width of frequency resolution (eV)') + # plt.xscale('log') + # #plt.yscale('log') + # plt.legend() + # plt.tight_layout() + # plt.savefig('res_vs_veff.pdf') + # plt.savefig('res_vs_veff.png', dpi=300) + plt.show() return True @@ -255,7 +309,10 @@ def BHomogeneityAndResNeeded(self, sensitivity_target): # needed_total_sigma = np.sqrt(needed_sigma_b**2 + needed_total_sigma) needed_total_sigma = np.sqrt(np.sum(sigmas**2)) needed_total_delta = np.sqrt(np.sum(deltas**2)) - return needed_b_error/self.MagneticField.nominal_field, needed_total_sigma, needed_total_delta + + needed_freq_sigma = sigmas[np.where(labels=='Start Frequency Resolution')] + needed_freq_delta = deltas[np.where(labels=='Start Frequency Resolution')] + return needed_b_error/self.MagneticField.nominal_field, needed_total_sigma, needed_total_delta, needed_freq_sigma, needed_freq_delta diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index e9a0e68f..99edc24c 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -23,7 +23,7 @@ def test_SensitivityCurveProcessor(self): "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "plot_path": "./sensitivity_curve.pdf", # optional - "track_length_axis": True, + "track_length_axis": False, "molecular_axis": False, "atomic_axis": True, "y_limits": [1e-1, 1e2], @@ -69,7 +69,8 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + 'sensitivity_target': [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] } sens = ConstantSensitivityParameterPlots("sensitivity_processor") sens.Configure(sens_config_dict) From e1a6ef3a6e27f9580a31f08347cf6a07d1118162 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 14 Nov 2022 15:39:02 -0800 Subject: [PATCH 034/262] cleaning --- .../ConstantSensitivityParameterPlots.py | 158 +++++++----------- .../Sensitivity/SensitivityCurveProcessor.py | 1 - tests/Sensitivity_test.py | 11 +- 3 files changed, 62 insertions(+), 108 deletions(-) diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index 4fdf3f23..b345b599 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -25,6 +25,7 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor +from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation from mermithid.misc.SensitivityFormulas import Sensitivity @@ -35,7 +36,7 @@ __all__ = [] __all__.append(__name__) -class ConstantSensitivityParameterPlots(BaseProcessor, Sensitivity): +class ConstantSensitivityParameterPlots(AnalyticSensitivityEstimation): ''' Description Args: @@ -59,15 +60,15 @@ def InternalConfigure(self, params): self.initialInhomogeneity = reader.read_param(params, 'initial_Inhomogeneity', 1e-8) self.veff_range = reader.read_param(params, 'veff_range', [0.001, 1]) - self.density_range = reader.read_param(params, 'density_range', [1.e16,1.e19]) + self.density_range = reader.read_param(params, 'density_range', [5.e15,1.e19]) self.BError_range = reader.read_param(params, 'BError_range', [1e-8,1e-3]) # Configuration is done in __init__ of SensitivityClass Sensitivity.__init__(self, self.config_file_path) - self.rhos = np.linspace(self.density_range[0], self.density_range[1], 20)/(m**3) + self.rhos = np.linspace(self.density_range[0], self.density_range[1], 1000)/(m**3) self.veffs = np.logspace(np.log10(self.veff_range[0]), np.log10(self.veff_range[1]), 25)*m**3 - self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 100) + self.Berrors = np.logspace(np.log10(self.BError_range[0]), np.log10(self.BError_range[1]), 2000) freq_res = np.empty(np.shape(self.rhos)) freq_res_delta = np.empty(np.shape(self.rhos)) @@ -76,24 +77,23 @@ def InternalConfigure(self, params): self.Experiment.number_density = self.rhos[i] freq_res[i], freq_res_delta[i] = self.syst_frequency_extraction() - print(freq_res_delta[i]) - plt.figure() + """plt.figure() plt.subplot(121) plt.plot(self.rhos*m**3, freq_res/eV) plt.xlabel('Density (/m³') - plt.ylabel('Energy width of frequency resolution (eV)') + plt.ylabel('Energy resolution from frequency uncertainty (eV)') plt.xscale('log') plt.subplot(122) plt.plot(self.rhos*m**3, freq_res_delta/eV) plt.xlabel('Density (/m³)') - plt.ylabel('Uncertainty on energy width of frequency resolution (eV)') + plt.ylabel('Uncertainty on energy resolution from frequency uncertainty (eV)') plt.xscale('log') plt.tight_layout() plt.savefig('Frequency_resolution_vs_rho.pdf') - plt.show() + plt.show()""" return True @@ -117,7 +117,7 @@ def InternalRun(self): for j in range(len(self.sensitivity_target)): for i, veff in enumerate(self.veffs): - print('\nVeff = {} m³'.format(veff/(m**3))) + logger.info('\nVeff = {} m³'.format(veff/(m**3))) self.Experiment.v_eff = veff self.MagneticField.inhomogenarity = self.initialInhomogeneity self.rho_opts[j, i]=self.FindOptimumPressure() @@ -126,7 +126,7 @@ def InternalRun(self): drho = self.density_range[1]/(m**3)*1.5 while np.abs(drho)> self.rho_opts[j, i] * 0.001 and n<10: - print('Iteration: {}'.format(n)) + logger.info('Iteration: {}'.format(n)) n+=1 new_rho = self.FindOptimumPressure() @@ -139,23 +139,12 @@ def InternalRun(self): index.append(np.where((self.CLs[j]/eVself.Berrors[1]))) - print('Achieved 90CL limits: {}'.format(self.CLs[j]/eV)) - print(index[j]) - time.sleep(2) + logger.info('Achieved 90CL limits: {}'.format(self.CLs[j]/eV)) + time.sleep(1) plt.figure(figsize=(10, 5)) - # plt.subplot(221) - # plt.plot(self.veffs[index]/(m**3), self.CLs[index]/eV) - # #plt.colorbar() - # plt.xlabel('Effective Volume (m³)') - # plt.ylabel('90% CL (eV)') - # plt.xscale('log') - # plt.yscale('log') - # plt.tight_layout() - - plt.subplot(121) for j in range(len(self.sensitivity_target)): @@ -207,31 +196,6 @@ def InternalRun(self): plt.savefig('res_vs_veff.pdf') plt.savefig('res_vs_veff.png', dpi=300) - # plt.figure(figsize=(10, 5)) - - # plt.subplot(121) - # for j in range(len(self.sensitivity_target)): - # plt.plot(self.veffs[index[j]]/(m**3), self.needed_freq_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) - # plt.xlabel('Effective Volume (m³)') - # plt.ylabel('Energy width of frequency resolution (eV)') - # plt.xscale('log') - # #plt.yscale('log') - # plt.legend() - # plt.tight_layout() - - # plt.subplot(122) - # for j in range(len(self.sensitivity_target)): - # plt.plot(self.veffs[index[j]]/(m**3), self.needed_freq_res_sigma[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) - # plt.xlabel('Effective Volume (m³)') - # plt.ylabel('Uncertainty on energy width of frequency resolution (eV)') - # plt.xscale('log') - # #plt.yscale('log') - # plt.legend() - # plt.tight_layout() - # plt.savefig('res_vs_veff.pdf') - # plt.savefig('res_vs_veff.png', dpi=300) - - plt.show() return True @@ -248,8 +212,6 @@ def DistanceToTargetVSBError(self, BError, sensitivity_target): return np.abs(self.sensitivity()/(eV**2)-sensitivity_target) def FindOptimumPressure(self): - - limit = [self.SensVSrho(rho)/eV for rho in self.rhos] opt_rho_index = np.argmin(limit) @@ -259,10 +221,11 @@ def FindOptimumPressure(self): result = minimize(self.SensVSrho, rho_opt, method='Nelder-Mead') if result.success: + logger.info('\tReplacing numerical value by actual optimiuation result.') rho_opt = result.x[0] limit = self.CL90(Experiment={"number_density": rho_opt})/eV - print('\tOptimum density: {}'.format(rho_opt*m**3)) + logger.info('\tOptimum density: {}'.format(rho_opt*m**3)) return rho_opt def FindMaxAllowedBerror(self, sensitivity_target): @@ -281,15 +244,10 @@ def FindMaxAllowedBerror(self, sensitivity_target): self.MagneticField.default_systematic_smearing = sig self.MagneticField.default_systematic_uncertainty = 0.05*sig - print('\tAllowed B relative error: {}'.format(Berror_opt)) return Berror_opt def BHomogeneityAndResNeeded(self, sensitivity_target): - - #sigma_sys = np.sqrt((self.sensitivity_target*eV**2)**2 - self.StatSens()**2) - #print('Stat sens: {}'.format(self.StatSens()/eV**2)) - neededBres = self.FindMaxAllowedBerror(sensitivity_target) labels, sigmas, deltas = self.get_systematics() # #b_sigma_tmp = (sigma_sys/4)**2 @@ -316,57 +274,57 @@ def BHomogeneityAndResNeeded(self, sensitivity_target): - # Sensitivity formulas: - # These are the functions that have so far been in the SensitivityClass but would not be used by a fake data experiment - # I did not include DeltaEWidth here becuase I consider it more general information about an experiment that could be used by the fake data studies too + # # Sensitivity formulas: + # # These are the functions that have so far been in the SensitivityClass but would not be used by a fake data experiment + # # I did not include DeltaEWidth here becuase I consider it more general information about an experiment that could be used by the fake data studies too - def StatSens(self): - """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" - sig_rate = self.SignalRate() - DeltaE = self.DeltaEWidth() - sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) - return sens + # def StatSens(self): + # """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + # sig_rate = self.SignalRate() + # DeltaE = self.DeltaEWidth() + # sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + # +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) + # return sens - def SystSens(self): - """Pure systematic componenet to sensitivity""" - labels, sigmas, deltas = self.get_systematics() - sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) - return sens + # def SystSens(self): + # """Pure systematic componenet to sensitivity""" + # labels, sigmas, deltas = self.get_systematics() + # sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + # return sens - def sensitivity(self, **kwargs): - """Combined statisical and systematic uncertainty. - Using kwargs settings in namespaces can be changed. - Example how to change number density which lives in namespace Experiment: - self.sensitivity(Experiment={"number_density": rho}) - """ - for sect, options in kwargs.items(): - for opt, val in options.items(): - self.__dict__[sect].__dict__[opt] = val + # def sensitivity(self, **kwargs): + # """Combined statisical and systematic uncertainty. + # Using kwargs settings in namespaces can be changed. + # Example how to change number density which lives in namespace Experiment: + # self.sensitivity(Experiment={"number_density": rho}) + # """ + # for sect, options in kwargs.items(): + # for opt, val in options.items(): + # self.__dict__[sect].__dict__[opt] = val - StatSens = self.StatSens() - SystSens = self.SystSens() + # StatSens = self.StatSens() + # SystSens = self.SystSens() - # Standard deviation on a measurement of m_beta**2 - sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) - return sigma_m_beta_2 + # # Standard deviation on a measurement of m_beta**2 + # sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + # return sigma_m_beta_2 - def CL90(self, **kwargs): - """ Gives 90% CL upper limit on neutrino mass.""" - # 90% of gaussian are contained in +-1.64 sigma region - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + # def CL90(self, **kwargs): + # """ Gives 90% CL upper limit on neutrino mass.""" + # # 90% of gaussian are contained in +-1.64 sigma region + # return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + # def sterial_m2_limit(self, Ue4_sq): + # return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) - # print functions - def print_statistics(self): - print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + # # print functions + # def print_statistics(self): + # print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - def print_systematics(self): - labels, sigmas, deltas = self.get_systematics() + # def print_systematics(self): + # labels, sigmas, deltas = self.get_systematics() - print() - for label, sigma, delta in zip(labels, sigmas, deltas): - print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file + # print() + # for label, sigma, delta in zip(labels, sigmas, deltas): + # print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file diff --git a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py index 01c086b1..27ab452e 100644 --- a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py @@ -139,7 +139,6 @@ def InternalRun(self): self.add_goal(value*eV, key) # if B is list plot line for each B - print(self.B_error) if isinstance(self.B_error, list) or isinstance(self.B_error, np.ndarray): N = len(self.B_error) for a, color in self.range(0, N): diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 99edc24c..94502101 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,8 +19,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", - "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "plot_path": "./sensitivity_curve.pdf", # optional "track_length_axis": False, @@ -49,8 +48,7 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } sens = AnalyticSensitivityEstimation("sensitivity_processor") sens.Configure(sens_config_dict) @@ -68,9 +66,8 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required - #"config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - "config_file_path": "/home/chrischtel/repos/another_termite/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", - 'sensitivity_target': [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] + "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "sensitivity_target": [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] } sens = ConstantSensitivityParameterPlots("sensitivity_processor") sens.Configure(sens_config_dict) From 4e921973ebfb973a8f84fae933ad421851e05955 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 14 Nov 2022 15:40:53 -0800 Subject: [PATCH 035/262] removing commented lines --- .../ConstantSensitivityParameterPlots.py | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index b345b599..514de430 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -273,58 +273,3 @@ def BHomogeneityAndResNeeded(self, sensitivity_target): return needed_b_error/self.MagneticField.nominal_field, needed_total_sigma, needed_total_delta, needed_freq_sigma, needed_freq_delta - - # # Sensitivity formulas: - # # These are the functions that have so far been in the SensitivityClass but would not be used by a fake data experiment - # # I did not include DeltaEWidth here becuase I consider it more general information about an experiment that could be used by the fake data studies too - - # def StatSens(self): - # """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" - # sig_rate = self.SignalRate() - # DeltaE = self.DeltaEWidth() - # sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - # +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) - # return sens - - # def SystSens(self): - # """Pure systematic componenet to sensitivity""" - # labels, sigmas, deltas = self.get_systematics() - # sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) - # return sens - - # def sensitivity(self, **kwargs): - # """Combined statisical and systematic uncertainty. - # Using kwargs settings in namespaces can be changed. - # Example how to change number density which lives in namespace Experiment: - # self.sensitivity(Experiment={"number_density": rho}) - # """ - # for sect, options in kwargs.items(): - # for opt, val in options.items(): - # self.__dict__[sect].__dict__[opt] = val - - # StatSens = self.StatSens() - # SystSens = self.SystSens() - - # # Standard deviation on a measurement of m_beta**2 - # sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) - # return sigma_m_beta_2 - - # def CL90(self, **kwargs): - # """ Gives 90% CL upper limit on neutrino mass.""" - # # 90% of gaussian are contained in +-1.64 sigma region - # return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - - # def sterial_m2_limit(self, Ue4_sq): - # return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) - - - # # print functions - # def print_statistics(self): - # print("Statistic", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - - # def print_systematics(self): - # labels, sigmas, deltas = self.get_systematics() - - # print() - # for label, sigma, delta in zip(labels, sigmas, deltas): - # print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file From dd09250837c8e6cd06e658f6f145e90435dce42c Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 7 Jun 2023 08:54:04 -0700 Subject: [PATCH 036/262] started working on cavity plots added new processor for cavity plots --- mermithid/misc/SensitivityCavityFormulas.py | 484 ++++++++++++++++++ mermithid/misc/__init__.py | 1 + .../CavitySensitivityCurveProcessor.py | 389 ++++++++++++++ mermithid/processors/Sensitivity/__init__.py | 1 + tests/Sensitivity_test.py | 68 +-- 5 files changed, 912 insertions(+), 31 deletions(-) create mode 100644 mermithid/misc/SensitivityCavityFormulas.py create mode 100644 mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py new file mode 100644 index 00000000..d63f7e3f --- /dev/null +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -0,0 +1,484 @@ +''' +Class calculating neutrino mass sensitivities based on analytic formulas from CDR. +Author: R. Reimann, C. Claessens +Date:11/17/2020 + +The statistical method and formulars are described in +CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. +''' +import numpy as np +import configparser +from numpy import pi + +# Numericalunits is a package to handle units and some natural constants +# natural constants +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day +from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck + +T0 = -273.15*K + +tritium_livetime = 5.605e8*s +tritium_mass_atomic = 3.016* amu *c0**2 +tritium_electron_crosssection_atomic = 1.1e-22*m**2 +tritium_endpoint_atomic = 18563.251*eV +last_1ev_fraction_atomic = 2.067914e-13/eV**3 + +tritium_mass_molecular = 6.032099 * amu *c0**2 +tritium_electron_crosssection_molecular = 3.487*1e-22*m**2 +tritium_endpoint_molecular = 18573.24*eV +last_1ev_fraction_molecular = 1.67364e-13/eV**3 + +ground_state_width = 0.436 * eV +ground_state_width_uncertainty = 0.01*0.436*eV + +gyro_mag_ratio_proton = 42.577*MHz/T + +# units that do not show up in numericalunits +# missing pre-factors +fW = W*1e-15 + +# unitless units, relative fractions +pc = 0.01 +ppm = 1e-6 +ppb = 1e-9 +ppt = 1e-12 +ppq = 1e-15 + +# radian and degree which are also not really units +rad = 1 +deg = np.pi/180 + + +try: + from morpho.utilities import morphologging + logger = morphologging.getLogger(__name__) +except: + print("Run without morpho!") + +class NameSpace(object): + def __init__(self, iteritems): + if type(iteritems) == dict: + iteritems = iteritems.items() + for k, v in iteritems: + setattr(self, k.lower(), v) + def __getattribute__(self, item): + return object.__getattribute__(self, item.lower()) + +############################################################################## +# CRES functions +def gamma(kin_energy): + return kin_energy/(me*c0**2) + 1 + +def beta(kin_energy): + # electron speed at kin_energy + return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) + +def frequency(kin_energy, magnetic_field): + # cyclotron frequency + return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field + +def wavelength(kin_energy, magnetic_field): + return c0/frequency(kin_energy, magnetic_field) + +def kin_energy(freq, magnetic_field): + return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) + +def rad_power(kin_energy, pitch, magnetic_field): + # electron radiation power + f = frequency(kin_energy, magnetic_field) + b = beta(kin_energy) + Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) + return Pe + +def track_length(rho, kin_energy=None, molecular=True): + if kin_energy is None: + kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic + crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic + return 1 / (rho * crosssect * beta(kin_energy) * c0) + +def sin2theta_sq_to_Ue4_sq(sin2theta_sq): + return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) + +def Ue4_sq_to_sin2theta_sq(Ue4_sq): + return 4*Ue4_sq*(1-Ue4_sq) + +############################################################################### +class CavitySensitivity(object): + """ + Documentation: + * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 + * Talias sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 + * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 + * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 + """ + def __init__(self, config_path): + self.cfg = configparser.ConfigParser() + with open(config_path, 'r') as configfile: + self.cfg.read_file(configfile) + + # display configuration + try: + logger.info("Config file content:") + for sect in self.cfg.sections(): + logger.info(' Section: {}'.format(sect)) + for k,v in self.cfg.items(sect): + logger.info(' {} = {}'.format(k,v)) + except: + pass + + self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) + + self.tau_tritium = tritium_livetime + if self.Experiment.atomic: + self.T_mass = tritium_mass_atomic + self.Te_crosssection = tritium_electron_crosssection_atomic + self.T_endpoint = tritium_endpoint_atomic + self.last_1ev_fraction = last_1ev_fraction_atomic + else: + self.T_mass = tritium_mass_molecular + self.Te_crosssection = tritium_electron_crosssection_molecular + self.T_endpoint = tritium_endpoint_molecular + self.last_1ev_fraction = last_1ev_fraction_molecular + + + self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) + self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) + self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) + self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) + self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + + self.CavityVolume() + + + # CAVITY + def CavityVolume(self): + self.TotalVolume = self.Experiment.length*np.pi*(0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field))**2 + self.EffectiveVolume = self.TotalVolume*self.Experiment.efficiency + + logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) + logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) + logger.info("Effective Volume: {} m^3".format(round(self.EffectiveVolume/m**3, 3))) + + # SENSITIVITY + def SignalRate(self): + """signal events in the energy interval before the endpoint, scale with DeltaE**3""" + + signal_rate = self.Experiment.number_density*self.TotalVolume*self.Experiment.efficiency*self.last_1ev_fraction/self.tau_tritium + if not self.Experiment.atomic: + if hasattr(self.Experiment, 'gas_fractions'): + avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) + signal_rate *= avg_n_T_atoms + else: + signal_rate *= 2 + return signal_rate + + def BackgroundRate(self): + """background rate, can be calculated from multiple components. + Assumes that background rate is constant over considered energy / frequency range.""" + return self.Experiment.background_rate_per_eV + + def SignalEvents(self): + """Number of signal events.""" + return self.SignalRate()*self.Experiment.LiveTime*self.DeltaEWidth()**3 + + def BackgroundEvents(self): + """Number of background events.""" + return self.BackgroundRate()*self.Experiment.LiveTime*self.DeltaEWidth() + + def DeltaEWidth(self): + """optimal energy bin width""" + labels, sigmas, deltas = self.get_systematics() + return np.sqrt(self.BackgroundRate()/self.SignalRate() + + 8*np.log(2)*(np.sum(sigmas**2))) + + def StatSens(self): + """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + sig_rate = self.SignalRate() + DeltaE = self.DeltaEWidth() + sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE + +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) + return sens + + def SystSens(self): + """Pure systematic componenet to sensitivity""" + labels, sigmas, deltas = self.get_systematics() + sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) + return sens + + def sensitivity(self, **kwargs): + """Combined statisical and systematic uncertainty. + Using kwargs settings in namespaces can be changed. + Example how to change number density which lives in namespace Experiment: + self.sensitivity(Experiment={"number_density": rho}) + """ + for sect, options in kwargs.items(): + for opt, val in options.items(): + self.__dict__[sect].__dict__[opt] = val + + StatSens = self.StatSens() + SystSens = self.SystSens() + + # Standard deviation on a measurement of m_beta**2 assuming a mass of zero + sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) + return sigma_m_beta_2 + + def CL90(self, **kwargs): + """ Gives 90% CL upper limit on neutrino mass.""" + # 90% of gaussian are contained in +-1.64 sigma region + return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + + def sterial_m2_limit(self, Ue4_sq): + return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + + # PHYSICS Functions + + def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas_fractions): + """ + Given gas composition info (H2 vs. other gases, and how much of each H2-type isotopolog), returns an average number of tritium atoms per gas particle. + + Inputs: + - gas_fractions: dict of composition fractions of each gas (different from scatter fractions!); all H2 isotopologs are combined under key 'H2' + - H2_type_gas_fractions: dict with fraction of each isotopolog, out of total amount of H2 + """ + H2_iso_avg_num = 0 + for (key, val) in H2_type_gas_fractions.items(): + if key=='T2': + H2_iso_avg_num += 2*val + elif key=='HT' or key=='DT': + H2_iso_avg_num += val + elif key=='H2' or key=='HD' or key=='D2': + pass + return gas_fractions['H2']*H2_iso_avg_num + + def BToKeErr(self, BErr, B): + return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) + + def KeToBerr(self, KeErr, B): + return KeErr/e*(2*np.pi*frequency(self.T_endpoint, B)/c0**2) + + def track_length(self, rho): + return track_length(rho, self.T_endpoint, not self.Experiment.atomic) + + # SYSTEMATICS + + def get_systematics(self): + """ Returns list of energy broadenings (sigmas) and + uncertainties on these energy broadenings (deltas) + for all considered systematics. We need to make sure + that we do not include effects twice or miss any + important effect. + + Returns: + * list of labels + * list of energy broadenings + * list of energy broadening uncertainties + """ + + # Different types of uncertainty contributions + sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() + sigma_f, delta_sigma_f = self.syst_frequency_extraction() + sigma_B, delta_sigma_B = self.syst_magnetic_field() + sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() + sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() + + labels = ["Thermal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] + sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] + deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] + + if not self.Experiment.atomic: + labels.append("Molecular final state") + sigmas.append(ground_state_width) + deltas.append(ground_state_width_uncertainty) + + return np.array(labels), np.array(sigmas), np.array(deltas) + + def print_statistics(self): + print("Statistical", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + + def print_systematics(self): + labels, sigmas, deltas = self.get_systematics() + + print() + for label, sigma, delta in zip(labels, sigmas, deltas): + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") + + def syst_doppler_broadening(self): + # estimated standard deviation of Doppler broadening distribution from + # translational motion of tritium atoms / molecules + # Predicted uncertainty on standard deviation, based on expected precision + # of temperature knowledge + if self.DopplerBroadening.UseFixedValue: + sigma = self.DopplerBroadening.Default_Systematic_Smearing + delta = self.DopplerBroadening.Default_Systematic_Uncertainty + return sigma, delta + + # termal doppler broardening + gasTemp = self.DopplerBroadening.gas_temperature + mass_T = self.T_mass + endpoint = self.T_endpoint + + # these factors are mainly neglidible in the recoil equation below + E_rec = 3.409 * eV # maximal value # same for molecular tritium? + mbeta = 0*eV # term neglidible + betanu = 1 # neutrinos are fast + # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu + # => expectation value of cosThetanu = 0.014 + cosThetaenu = 0.014 + + Ke = endpoint + betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke + Emax = endpoint + me*c0**2 + Ee = endpoint + me*c0**2 + p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) + sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) + + delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + return sigma_trans, delta_trans + + + def syst_frequency_extraction(self): + # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) + # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? + # Are we double counting the effect of magnetic field uncertainty here? + # Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? + + if self.FrequencyExtraction.UseFixedValue: + sigma = self.FrequencyExtraction.Default_Systematic_Smearing + delta = self.FrequencyExtraction.Default_Systematic_Uncertainty + return sigma, delta + + # Cramer-Rao lower bound / how much worse are we than the lower bound + ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor + ts = self.FrequencyExtraction.track_timestep + # "This is apparent in the case of resonant patch antennas and cavities, in which the time scale of the signal onset is set by the Q-factor of the resonant structure." + # You can get it from the finite impulse response of the antennas from HFSS + Gdot = self.FrequencyExtraction.track_onset_rate + + fEndpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) + betae = beta(self.T_endpoint) + Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) + alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope + # quantum limited noise + sigNoise = np.sqrt((2*pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level + Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) + Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts + + # sigma_f from Cramer-Rao lower bound in Hz + sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) + + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) + # uncertainty in alpha + delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) + # uncetainty in sigma_f in Hz due to uncertainty in alpha + delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) + + # sigma_f from Cramer-Rao lower bound in eV + sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 + delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 + + # combined sigma_f in eV + sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) + + # the magnetic_field_smearing and uncertainty added here consider the following effect: + # thinking in terms of a phase II track, there is some smearing of the track / width of the track which influences the frequency extraction + # this does not account for any effect comming from converting frequency to energy + # the reason behind the track width / smearing is the change in B field that the electron sees within one axial oscillation. + # Depending on the trap shape this smearing may be different. + + if self.FrequencyExtraction.UseFixedUncertainty: + return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f + else: + + return sigma_f, delta_sigma_f + + def syst_magnetic_field(self): + + # magnetic field uncertainties can be decomposed in several part + # * true magnetic field inhomogeneity + # (would be there also without a trap) + # * magnetic field calibration has uncertainties + # (would be there also without a trap) + # * position / pitch angle reconstruction has uncertainties + # (this can even be the degenerancy we see for harmonic traps) + # (depends on trap shape) + + if self.MagneticField.UseFixedValue: + sigma = self.MagneticField.Default_Systematic_Smearing + delta = self.MagneticField.Default_Systematic_Uncertainty + return sigma, delta + + B = self.MagneticField.nominal_field + if self.MagneticField.useinhomogenarity: + inhomogenarity = self.MagneticField.inhomogenarity + sigma = self.BToKeErr(inhomogenarity*B, B) + return sigma, 0.05*sigma + + BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability + delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution + + BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field + delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? + + Delta_t_since_calib = self.MagneticField.time_since_calibration + shiftBdot = self.MagneticField.shift_Bdot + smearBdot = self.MagneticField.smear_Bdot + delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot + delta_smearBdot = self.MagneticField.uncertainty_smearBdot + BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) + delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) + + # position uncertainty is linear in wavelength + # position uncertainty is nearly constant w.r.t. radial position + # based on https://3.basecamp.com/3700981/buckets/3107037/uploads/3442593126 + rRecoErr = self.MagneticField.rRecoErr + delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr + + rRecoPhiErr = self.MagneticField.rRecoPhiErr + delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr + + rProbeErr = self.MagneticField.rProbeErr + delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr + + rProbePhiErr = self.MagneticField.rProbePhiErr + delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr + + Berr = np.sqrt(BMapErr**2 + + BFlatErr**2 + + BdotErr**2 + + rRecoErr**2 + + rRecoPhiErr**2 + + rProbeErr**2 + + rProbePhiErr**2) + + delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + + BFlatErr**2 * delta_BFlatErr**2 + + BdotErr**2 * delta_BdotErr**2 + + rRecoErr**2 * delta_rRecoErr**2 + + rRecoPhiErr**2 * delta_rRecoPhiErr**2 + + rProbeErr**2 * delta_rProbeErr**2 + + rProbePhiErr**2 * delta_rProbePhiErr**2) + + return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) + + def syst_missing_tracks(self): + # this systematic should describe the energy broadening due to the line shape. + # Line shape is caused because you miss the first n tracks but then detect the n+1 + # track and you assume that this is the start frequency. + # This depends on the gas composition, density and cross-section. + if self.MissingTracks.UseFixedValue: + sigma = self.MissingTracks.Default_Systematic_Smearing + delta = self.MissingTracks.Default_Systematic_Uncertainty + return sigma, delta + else: + raise NotImplementedError("Missing track systematic is not implemented.") + + def syst_plasma_effects(self): + if self.PlasmaEffects.UseFixedValue: + sigma = self.PlasmaEffects.Default_Systematic_Smearing + delta = self.PlasmaEffects.Default_Systematic_Uncertainty + return sigma, delta + else: + raise NotImplementedError("Plasma effect sysstematic is not implemented.") diff --git a/mermithid/misc/__init__.py b/mermithid/misc/__init__.py index dc52a22d..44f5ec91 100644 --- a/mermithid/misc/__init__.py +++ b/mermithid/misc/__init__.py @@ -9,3 +9,4 @@ from . import FakeTritiumDataFunctions from . import ConversionFunctions from . import SensitivityFormulas +from . import SensitivityCavityFormulas diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py new file mode 100644 index 00000000..45bfdab1 --- /dev/null +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -0,0 +1,389 @@ +''' +Calculate sensitivity curve and plot vs. pressure +function. +Author: R. Reimann, C. Claessens +Date:11/17/2020 + +More description +''' + +from __future__ import absolute_import + + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + + +# Numericalunits is a package to handle units and some natural constants +# natural constants +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day +ppm = 1e-6 +ppb = 1e-9 + +# morpho imports +from morpho.utilities import morphologging, reader +from morpho.processors import BaseProcessor +from mermithid.misc.SensitivityFormulas import Sensitivity +from mermithid.misc.SensitivityCavityFormulas import CavitySensitivity + + +logger = morphologging.getLogger(__name__) + + + +__all__ = [] +__all__.append(__name__) + +class CavitySensitivityCurveProcessor(BaseProcessor): + ''' + Description + Args: + + Inputs: + + Output: + + ''' + def InternalConfigure(self, params): + ''' + Configure + ''' + # file paths + self.config_file_path = reader.read_param(params, 'config_file_path', "required") + self.comparison_config_file_path = reader.read_param(params, 'comparison_config_file_path', '') + self.plot_path = reader.read_param(params, 'plot_path', "required") + + + # labels + self.main_curve_upper_label = reader.read_param(params, 'main_curve_upper_label', r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") + self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") + self.comparison_curve_label = reader.read_param(params, 'comparison_curve_label', r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") + + + # options + self.comparison_curve = reader.read_param(params, 'comparison_curve', False) + self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) + self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) + + + # plot configurations + self.figsize = reader.read_param(params, 'figsize', (6,6)) + self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) + self.efficiency_range = reader.read_param(params, 'efficiency_range', [0.001,0.1]) + self.year_range = reader.read_param(params, "years_range", [0.1, 10]) + self.density_axis = reader.read_param(params, "density_axis", True) + self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) + self.track_length_axis = reader.read_param(params, 'track_length_axis', True) + self.atomic_axis = reader.read_param(params, 'atomic_axis', False) + self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) + self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) + self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) + self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + + if self.density_axis: + self.add_sens_line = self.add_density_sens_line + logger.info("Doing density lines") + else: + self.add_sens_line = self.add_exposure_sens_line + logger.info("Doing exposure lines") + + + # goals + self.goals = reader.read_param(params, "goals", {}) + + + # setup sensitivities + self.cavity = reader.read_param(params, 'cavity', False) + + if self.cavity: + self.sens_main = CavitySensitivity(self.config_file_path) + else: + self.sens_main = Sensitivity(self.config_file_path) + self.sens_main_is_atomic = self.sens_main.Experiment.atomic + + if self.comparison_curve: + if self.cavity: + self.sens_ref = CavitySensitivity(self.comparison_config_file_path) + else: + self.sens_ref = Sensitivity(self.comparison_config_file_path) + self.sens_ref_is_atomic = self.sens_ref.Experiment.atomic + + # check atomic and molecular + if self.molecular_axis: + if not self.sens_main_is_atomic: + self.molecular_sens = self.sens_main + logger.info("Main curve is molecular") + elif not self.sens_ref_is_atomic: + self.molecular_sens = self.sens_ref + logger.info("Comparison curve is molecular") + else: + raise ValueError("No experiment is configured to be molecular") + + if self.atomic_axis: + if self.sens_main_is_atomic: + self.atomic_sens = self.sens_main + logger.info("Main curve is atomic") + elif self.sens_ref_is_atomic: + self.atomic_sens = self.sens_ref + logger.info("Comparison curve is atomic") + else: + raise ValueError("No experiment is configured to be atomic") + + # densities + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 + self.effs = np.logspace(np.log10(self.efficiency_range[0]), np.log10(self.efficiency_range[1]), 10) + self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 10)/year + + return True + + + + def InternalRun(self): + + self.create_plot() + + # add second and third x axis for track lengths + if self.track_length_axis: + self.add_track_length_axis() + + # add line for comparison using second config + if self.comparison_curve: + self.add_comparison_curve(label=self.comparison_curve_label) + #self.add_arrow(self.sens_main) + + for key, value in self.goals.items(): + logger.info('Adding goal: {}'.format(key)) + self.add_goal(value*eV, key) + + # first optimize density + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit) + rho_opt = self.rhos[opt_ref] + self.sens_main.Experiment.number_density = rho_opt + + # if B is list plot line for each B + if isinstance(self.B_error, list) or isinstance(self.B_error, np.ndarray): + N = len(self.B_error) + for a, color in self.range(0, N): + sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) + self.sens_main.MagneticField.usefixedvalue = True + self.sens_main.MagneticField.default_systematic_smearing = sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + self.add_sens_line(self.sens_main, color=color) + self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color="darkblue") + self.add_text(self.label_x_position, self.lower_label_y_position, self.main_curve_lower_label, color="darkred") + + else: + sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) + self.sens_main.MagneticField.usefixedvalue = True + self.sens_main.MagneticField.default_systematic_smearing = sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + self.add_sens_line(self.sens_main, color='blue') + self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label) + + + # save plot + self.save(self.plot_path) + + # PRINT OPTIMUM RESULTS + + # print number of events + # for minimum field smearing + sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*np.min(self.B_error), self.sens_main.MagneticField.nominal_field) + self.sens_main.MagneticField.usefixedvalue = True + self.sens_main.MagneticField.default_systematic_smearing = sig + self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + + + + + logger.info('Main curve (veff = {} m**3, rho = {} /m**3):'.format(self.sens_main.EffectiveVolume/(m**3), rho_opt*(m**3))) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.EffectiveVolume)) + logger.info('Total signal: {}'.format(rho_opt*self.sens_main.EffectiveVolume* + self.sens_main.Experiment.LiveTime/ + self.sens_main.tau_tritium*2)) + logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* + rho_opt*self.sens_main.EffectiveVolume* + self.sens_main.Experiment.LiveTime/ + self.sens_main.tau_tritium*2)) + + self.sens_main.print_statistics() + self.sens_main.print_systematics() + + return True + + + def create_plot(self): + # setup axis + self.fig, self.ax = plt.subplots(figsize=self.figsize) + ax = self.ax + ax.set_xscale("log") + ax.set_yscale("log") + if self.density_axis: + logger.info("Adding density axis") + ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) + + if self.atomic_axis and self.molecular_axis: + axis_label = r"(Atomic / molecular) number density $\rho\, \, (\mathrm{m}^{-3})$" + elif self.atomic_axis: + axis_label = r"(Atomic) number density $\rho\, \, (\mathrm{m}^{-3})$" + elif self.molecular_axis: + axis_label = r"(Molecular) number density $\rho\, \, (\mathrm{m}^{-3})$" + else: + axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" + + + + else: + logger.info("Adding efficiency axis") + ax.set_xlim(self.effs[0], self.effs[-1]) + axis_label = r"Efficiency" + + ax.set_xlabel(axis_label) + ax.set_ylim(self.ylim) + ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + + def add_track_length_axis(self): + if self.atomic_axis: + ax2 = self.ax.twiny() + ax2.set_xscale("log") + ax2.set_xlabel("(Atomic) track length (s)") + ax2.set_xlim(self.atomic_sens.track_length(self.rhos[0])/s, + self.atomic_sens.track_length(self.rhos[-1])/s) + + if self.molecular_axis: + ax3 = self.ax.twiny() + + if self.atomic_axis: + ax3.spines["top"].set_position(("axes", 1.2)) + ax3.set_frame_on(True) + ax3.patch.set_visible(False) + for sp in ax3.spines.values(): + sp.set_visible(False) + ax3.spines["top"].set_visible(True) + + ax3.set_xscale("log") + ax3.set_xlabel("(Molecular) track length (s)") + ax3.set_xlim(self.molecular_sens.track_length(self.rhos[0])/s, + self.molecular_sens.track_length(self.rhos[-1])/s) + + else: + logger.warning("No track length axis added since neither atomic nor molecular was requested") + self.fig.tight_layout() + + """def add_density_comparison_curve(self, label, color='k'): + limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.opt_ref = np.argmin(limit) + + rho_opt = self.rhos[self.opt_ref] + logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.Experiment.v_eff/(m**3))) + logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff)) + logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff* + self.sens_ref.Experiment.LiveTime/ + self.sens_ref.tau_tritium)) + + self.ax.plot(self.rhos*m**3, limit, color=color) + self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) + + #self.ax.axhline(0.04, color="gray", ls="--") + #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") + self.ax.text(1.5e14, 0.11, label)""" + + def add_comparison_curve(self, label, color='k'): + + + limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit) + rho_opt = self.rhos[opt_ref] + + logger.info('Ref. optimum density: {} /m**3'.format(rho_opt*m**3)) + logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.EffectiveVolume/(m**3))) + logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.EffectiveVolume)) + logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.EffectiveVolume* + self.sens_ref.Experiment.LiveTime/ + self.sens_ref.tau_tritium)) + logger.info('CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) + + limits = self.add_sens_line(self.sens_ref, color=color) + + #self.ax.axhline(0.04, color="gray", ls="--") + #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") + self.ax.text(self.label_x_position, 0.042, label) + + def add_arrow(self, sens): + if not hasattr(self, "opt_ref"): + self.add_comparison_curve() + + def get_relative(val, axis): + xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() + return (np.log10(val)-np.log10(xmin))/(np.log10(xmax)-np.log10(xmin)) + + rho_IV = self.rhos[self.opt_ref] + track_length_IV = self.sens_ref.track_length(rho_IV) + track_length_III = self.sens_main.track_length(rho_IV) + rho_III = rho_IV*track_length_III/track_length_IV + limit_III = sens.CL90(Experiment={"number_density": rho_III}) + + x_start = get_relative(rho_IV*m**3, "x") + y_start = get_relative(2*limit_III/eV, "y") + x_stop = get_relative(rho_III*m**3, "x") + y_stop = get_relative(limit_III/eV, "y") + plt.arrow(x_start, y_start, x_stop-x_start, y_stop-y_start, + transform = self.ax.transAxes, + facecolor = 'black', + edgecolor='k', + length_includes_head=True, + head_width=0.02, + head_length=0.03, + ) + + def add_goal(self, value, label): + self.ax.axhline(value/eV, color="gray", ls="--") + self.ax.text(self.goal_x_pos, 0.75*value/eV, label) + + def add_density_sens_line(self, sens, **kwargs): + limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + self.ax.plot(self.rhos*m**3, limits, **kwargs) + logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) + return limits + + def add_exposure_sens_line(self, sens, **kwargs): + limits = [] + for eff in self.effs: + sens.Experiment.efficiency = eff + sens.CavityVolume() + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit) + rho_opt = self.rhos[opt_ref] + sens.Experiment.number_density = rho_opt + limits.append(sens.CL90()/eV) + print(limits) + self.ax.plot(self.effs, limits, **kwargs) + + def add_text(self, x, y, text, color="k"): + self.ax.text(x, y, text, color=color) + + def range(self, start, stop): + cmap = matplotlib.cm.get_cmap('Spectral') + norm = matplotlib.colors.Normalize(vmin=start, vmax=stop-1) + return [(idx, cmap(norm(idx))) for idx in range(start, stop)] + + def save(self, savepath, **kwargs): + self.fig.tight_layout() + #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) + metadata = {"Author": "p8/mermithid", + "Title": "Neutrino mass sensitivity", + "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero." + } + #"Keywords": keywords} + if savepath is not None: + self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=300, metadata=metadata) + self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) + + diff --git a/mermithid/processors/Sensitivity/__init__.py b/mermithid/processors/Sensitivity/__init__.py index 1b2d3e16..4c973521 100644 --- a/mermithid/processors/Sensitivity/__init__.py +++ b/mermithid/processors/Sensitivity/__init__.py @@ -4,5 +4,6 @@ from __future__ import absolute_import from .SensitivityCurveProcessor import SensitivityCurveProcessor +from .CavitySensitivityCurveProcessor import CavitySensitivityCurveProcessor from .AnalyticSensitivityEstimation import AnalyticSensitivityEstimation from .ConstantSensitivityParameterPlots import ConstantSensitivityParameterPlots diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 94502101..f3979011 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -14,31 +14,37 @@ class SensitivityTest(unittest.TestCase): def test_SensitivityCurveProcessor(self): - from mermithid.processors.Sensitivity import SensitivityCurveProcessor + from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor sens_config_dict = { # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", - "plot_path": "./sensitivity_curve.pdf", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_262MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_efficiency_curve.pdf", # optional "track_length_axis": False, "molecular_axis": False, - "atomic_axis": True, - "y_limits": [1e-1, 1e2], - "density_range": [1e12,1e21], - "main_curve_upper_label": r"Atomic CRES at 0.04 T"+"\n"+r"$V_\mathrm{eff} = 0.01\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", - "main_curve_lower_label": r"$\sigma_B = 0.16\,\mathrm{ppm}$", - "goals": {"Phase III (0.4 eV)": 0.4},# "Phase IV (40 meV)": 0.04}, - "comparison_curve": False, - "comparison_config_file_path": "/host_scripts/rreimann/data/Sensitivity/Config_PhaseIV_atomic_V_eff_5m3.cfg", - "B_inhomogeneity": np.linspace(0.16, 2.0, 10)*1e-6, - "B_inhom_uncertainty": 0.05, - "lower_label_y_position": 0.5, - "upper_label_y_position": 5, - "label_x_position": 1e14 + "atomic_axis": False, + "density_axis": False, + "cavity": True, + "y_limits": [2e-2, 3], + "density_range": [1e12,1e18], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Molecular"+"\n"+"1 year"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", + "main_curve_lower_label": r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_262MHz_Experiment.cfg", + "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + "B_inhom_uncertainty": 0.01, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.0002 #2e12 #0.0002 } - sens_curve = SensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) sens_curve.Run() @@ -48,17 +54,17 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } - sens = AnalyticSensitivityEstimation("sensitivity_processor") - sens.Configure(sens_config_dict) - sens.Run() + # sens = AnalyticSensitivityEstimation("sensitivity_processor") + # sens.Configure(sens_config_dict) + # sens.Run() - sens.print_statistics() - sens.print_systematics() + # sens.print_statistics() + # sens.print_systematics() - results = sens.results - logger.info(results) + # results = sens.results + # logger.info(results) def test_ConstantSensitivityCurvesProcessor(self): from mermithid.processors.Sensitivity import ConstantSensitivityParameterPlots @@ -66,15 +72,15 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "config_file_path": "//host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "sensitivity_target": [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] } - sens = ConstantSensitivityParameterPlots("sensitivity_processor") - sens.Configure(sens_config_dict) - sens.Run() + #sens = ConstantSensitivityParameterPlots("sensitivity_processor") + #sens.Configure(sens_config_dict) + #sens.Run() - sens.print_statistics() - sens.print_systematics() + #sens.print_statistics() + #sens.print_systematics() From 0ca32ba2c2d497ebbe9de98ba93e04d443821891 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 7 Jun 2023 17:14:32 -0700 Subject: [PATCH 037/262] added Wouter's crlb calculation --- mermithid/misc/SensitivityCavityFormulas.py | 156 +++++++++++++++++--- 1 file changed, 133 insertions(+), 23 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index d63f7e3f..ac397128 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -86,11 +86,15 @@ def wavelength(kin_energy, magnetic_field): def kin_energy(freq, magnetic_field): return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) -def rad_power(kin_energy, pitch, magnetic_field): +"""def rad_power(kin_energy, pitch, magnetic_field): # electron radiation power f = frequency(kin_energy, magnetic_field) b = beta(kin_energy) Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) + return Pe""" + +def rad_power(kin_energy, pitch, magnetic_field): + Pe = 2*(e**2*magnetic_field*np.sin(pitch))**2*(gamma(kin_energy)**2-1)/(12*eps0*c0*np.pi*me**2) return Pe def track_length(rho, kin_energy=None, molecular=True): @@ -105,6 +109,71 @@ def sin2theta_sq_to_Ue4_sq(sin2theta_sq): def Ue4_sq_to_sin2theta_sq(Ue4_sq): return 4*Ue4_sq*(1-Ue4_sq) +# Wouters functinos +def db_to_pwr_ratio(q_db): + return 10**(q_db/10) + +def axial_frequency(length, kin_energy, max_pitch_angle=86): + pitch_max = max_pitch_angle/180*np.pi + return (beta(kin_energy)*c0*np.cos(pitch_max)) / (2*length) + +def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, max_pitch_angle=86): + # Because of the differenct electron trajectories in the trap, + # An electron will see a slightly different magnetic field + # depending on its position in the trap, especially the pitch angle. + # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. + y = (90-max_pitch_angle)/5 + return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) + +# Noise power entering the amplifier, inclding the transmitted noise from the cavity and the reflected noise from the circulator. +# Insertion loss is included. +def Pn_dut_entrance(t_cavity, + t_amplifier, + att_line_db, + att_cir_db, + coupling, + freq, + bandwidth, + loaded_Q): + att_cir = db_to_pwr_ratio(att_cir_db) + att_line = db_to_pwr_ratio(att_line_db) + assert( (np.all(att_cir<=1)) & (np.all(att_line<=1)) ) + + # Calculate the noise at the cavity + Pn_cav = Pn_cavity(t_cavity, coupling, loaded_Q, bandwidth, freq) + Pn_circulator = t_effective(t_amplifier, freq)*kB*bandwidth + Pn_circulator_after_reflection = Pn_reflected(Pn_f(Pn_circulator,t_amplifier, t_cavity,att_line, bandwidth), coupling, loaded_Q, bandwidth, freq) + # Propagate the noise over the line towards the circulator + Pn_entrance = Pn_f(Pn_circulator_after_reflection+Pn_cav,t_cavity,t_amplifier,att_line,bandwidth) + # Apply the effect of the circulator + return Pn_f(Pn_entrance,t_amplifier,t_amplifier,att_cir,bandwidth) + +# Noise power genrated in the cavity integrated over the bandwidth that couples into the readout line. +def Pn_cavity(t_cavity, coupling, loaded_Q, bandwidth, freq): + return kB*t_effective(t_cavity, freq)*4*coupling/(1+coupling)**2*freq/loaded_Q*np.arctan(loaded_Q*bandwidth/freq) + +# Noise power reflecting of the cavity +def Pn_reflected(Pn_incident, coupling, loaded_Q, bandwidth, freq): + + reflection_coefficient = 1-freq/loaded_Q/bandwidth*np.arctan(loaded_Q*bandwidth/freq)*4*coupling/(1+coupling)**2 + return Pn_incident*reflection_coefficient + +# Power at the end of a lossy line with temperature gradient +def Pn_f(Pn_i,t_i,t_f,a,bandwidth): # eq 10 + if hasattr(a, "__len__") or a!=1: + return Pn_i+ kB*bandwidth*(t_f-t_i)*(1+ (1-a)/np.log(a))+ (t_i*kB*bandwidth-Pn_i)*(1-a) + else: + return Pn_i*np.ones_like(t_f) + +# Effective temperature taking the quantum photon population into account. +def t_effective(t_physical, cyclotron_frequency): + quantum = 2*np.pi*hbar*cyclotron_frequency/kB + #for numerical stability + if np.all(quantum/t_physical < 1e-2): + return t_physical + else: + return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) + ############################################################################### class CavitySensitivity(object): """ @@ -154,12 +223,19 @@ def __init__(self, config_path): # CAVITY + def CavityRadius(self): + axial_mode_index = 1 + return c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.L_over_D**2)) + def CavityVolume(self): - self.TotalVolume = self.Experiment.length*np.pi*(0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field))**2 + #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) + radius = self.CavityRadius() + self.TotalVolume = 2*radius*self.Experiment.L_over_D*np.pi*(radius)**2 self.EffectiveVolume = self.TotalVolume*self.Experiment.efficiency logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) + logger.info("Radius: {} cm".format(round(radius/cm, 3))) logger.info("Effective Volume: {} m^3".format(round(self.EffectiveVolume/m**3, 3))) # SENSITIVITY @@ -342,15 +418,13 @@ def syst_doppler_broadening(self): def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? - # Are we double counting the effect of magnetic field uncertainty here? - # Is 'sigma_f_Bfield' the same as 'rRecoErr', 'delta_rRecoErr', 'rRecoPhiErr', 'delta_rRecoPhiErr'? - + if self.FrequencyExtraction.UseFixedValue: sigma = self.FrequencyExtraction.Default_Systematic_Smearing delta = self.FrequencyExtraction.Default_Systematic_Uncertainty return sigma, delta - # Cramer-Rao lower bound / how much worse are we than the lower bound + """ # Cramer-Rao lower bound / how much worse are we than the lower bound ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor ts = self.FrequencyExtraction.track_timestep # "This is apparent in the case of resonant patch antennas and cavities, in which the time scale of the signal onset is set by the Q-factor of the resonant structure." @@ -368,31 +442,67 @@ def syst_frequency_extraction(self): # sigma_f from Cramer-Rao lower bound in Hz sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) - + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4)))) - # uncertainty in alpha + + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4))))""" + + + endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + # using Pe and alpha (aka slope) from above + Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) + alpha_approx = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope + + # Using Wouter's calculation: + + # Total required bandwidth is the sum of the endpoint region and the axial frequency. + # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI + required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint) + required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, length_diameter_ratio=self.Experiment.L_over_D) + required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting + + # Cavity coupling + loaded_Q = endpoint_frequency/required_bw # FWHM + coupling = self.FrequencyExtraction.unloaded_q/loaded_Q-1 + + # Attenuation frequency dependence at hoc method + att_cir_db = -0.3 + att_line_db = -0.05 + att_cir_db_freq = att_cir_db*(1+endpoint_frequency/(10*GHz)) + att_line_db_freq = att_line_db*(1+endpoint_frequency/(10*GHz)) + + # Noise power for bandwidth set by density/track length + time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) + fft_bandwidth = 3/time_window #(delta f) is the frequency bandwidth of interest. We have a main carrier and 2 axial side bands, so 3*(FFT bin width) + tn_fft = Pn_dut_entrance(self.FrequencyExtraction.cavity_temperature, + self.FrequencyExtraction.amplifier_temperature, + att_line_db_freq,att_cir_db_freq, + coupling, + endpoint_frequency, + fft_bandwidth,loaded_Q)/kB/fft_bandwidth + + # Noise temperature of amplifier + tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency + tn_system_fft = tn_amplifier+tn_fft + + P_signal_received = Pe*self.FrequencyExtraction.epsilon_collection*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) + tau_snr = kB*tn_system_fft/P_signal_received + + sigma_f_CRLB = np.sqrt((20*(alpha_approx*tau_snr)**2 + 180*tau_snr/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + + # end of Wouter's calculation + + """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) # uncetainty in sigma_f in Hz due to uncertainty in alpha - delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2) + delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" # sigma_f from Cramer-Rao lower bound in eV - sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*sigma_f_CRLB*c0**2 - delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*fEndpoint**2)*delta_sigma_f_CRLB*c0**2 + sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 + # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*delta_sigma_f_CRLB*c0**2 # combined sigma_f in eV sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) - delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) - - # the magnetic_field_smearing and uncertainty added here consider the following effect: - # thinking in terms of a phase II track, there is some smearing of the track / width of the track which influences the frequency extraction - # this does not account for any effect comming from converting frequency to energy - # the reason behind the track width / smearing is the change in B field that the electron sees within one axial oscillation. - # Depending on the trap shape this smearing may be different. + # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) - if self.FrequencyExtraction.UseFixedUncertainty: - return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f - else: - - return sigma_f, delta_sigma_f + return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f def syst_magnetic_field(self): From b59e1463e3d7722047ee356a831405d01b9ec710 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 7 Jun 2023 18:06:35 -0700 Subject: [PATCH 038/262] zeroed final state uncertainty --- mermithid/misc/SensitivityCavityFormulas.py | 2 +- tests/Sensitivity_test.py | 31 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index ac397128..aee22f8a 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -32,7 +32,7 @@ last_1ev_fraction_molecular = 1.67364e-13/eV**3 ground_state_width = 0.436 * eV -ground_state_width_uncertainty = 0.01*0.436*eV +ground_state_width_uncertainty = 0.0*0.436*eV gyro_mag_ratio_proton = 42.577*MHz/T diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index f3979011..0b66f0f0 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -47,6 +47,37 @@ def test_SensitivityCurveProcessor(self): sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) sens_curve.Run() + + sens_config_dict = { + # required + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_262MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_curve.pdf", + # optional + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 3], + "density_range": [1e12,1e18], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Molecular"+"\n"+"1 year"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", + "main_curve_lower_label": r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_262MHz_Experiment.cfg", + "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + "B_inhom_uncertainty": 0.01, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 1.5e15, #0.02, #1e14, + "goals_x_position": 2e12 #0.0002 + } + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 25e74cfd20292d40e8d2d5ee15da1289781ba844 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 8 Jun 2023 18:34:31 -0700 Subject: [PATCH 039/262] added crlb for 0 slope and decision for when to use which crlb --- mermithid/misc/SensitivityCavityFormulas.py | 98 +++++++++++++-------- tests/Sensitivity_test.py | 8 +- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index aee22f8a..e2086571 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -15,7 +15,7 @@ from numericalunits import e, me, c0, eps0, kB, hbar from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W -from numericalunits import hour, year, day +from numericalunits import hour, year, day, s, ms from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck T0 = -273.15*K @@ -414,6 +414,47 @@ def syst_doppler_broadening(self): delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) return sigma_trans, delta_trans + def calculate_tau_snr(self, time_window): + + endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) + + # Using Wouter's calculation: + # Total required bandwidth is the sum of the endpoint region and the axial frequency. + # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI + required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint) + required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, length_diameter_ratio=self.Experiment.L_over_D) + required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting + + # Cavity coupling + loaded_Q = endpoint_frequency/required_bw # FWHM + coupling = self.FrequencyExtraction.unloaded_q/loaded_Q-1 + + # Attenuation frequency dependence at hoc method + att_cir_db = -0.3 + att_line_db = -0.05 + att_cir_db_freq = att_cir_db*(1+endpoint_frequency/(10*GHz)) + att_line_db_freq = att_line_db*(1+endpoint_frequency/(10*GHz)) + + # Noise power for bandwidth set by density/track length + fft_bandwidth = 3/time_window #(delta f) is the frequency bandwidth of interest. We have a main carrier and 2 axial side bands, so 3*(FFT bin width) + tn_fft = Pn_dut_entrance(self.FrequencyExtraction.cavity_temperature, + self.FrequencyExtraction.amplifier_temperature, + att_line_db_freq,att_cir_db_freq, + coupling, + endpoint_frequency, + fft_bandwidth,loaded_Q)/kB/fft_bandwidth + + # Noise temperature of amplifier + tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency + tn_system_fft = tn_amplifier+tn_fft + + P_signal_received = Pe*self.FrequencyExtraction.epsilon_collection*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) + tau_snr = kB*tn_system_fft/P_signal_received + + # end of Wouter's calculation + return tau_snr + def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) @@ -449,45 +490,32 @@ def syst_frequency_extraction(self): # using Pe and alpha (aka slope) from above Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) alpha_approx = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope - - # Using Wouter's calculation: + time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - # Total required bandwidth is the sum of the endpoint region and the axial frequency. - # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI - required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint) - required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, length_diameter_ratio=self.Experiment.L_over_D) - required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting - - # Cavity coupling - loaded_Q = endpoint_frequency/required_bw # FWHM - coupling = self.FrequencyExtraction.unloaded_q/loaded_Q-1 - - # Attenuation frequency dependence at hoc method - att_cir_db = -0.3 - att_line_db = -0.05 - att_cir_db_freq = att_cir_db*(1+endpoint_frequency/(10*GHz)) - att_line_db_freq = att_line_db*(1+endpoint_frequency/(10*GHz)) + time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/alpha_approx - # Noise power for bandwidth set by density/track length - time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - fft_bandwidth = 3/time_window #(delta f) is the frequency bandwidth of interest. We have a main carrier and 2 axial side bands, so 3*(FFT bin width) - tn_fft = Pn_dut_entrance(self.FrequencyExtraction.cavity_temperature, - self.FrequencyExtraction.amplifier_temperature, - att_line_db_freq,att_cir_db_freq, - coupling, - endpoint_frequency, - fft_bandwidth,loaded_Q)/kB/fft_bandwidth - - # Noise temperature of amplifier - tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency - tn_system_fft = tn_amplifier+tn_fft + tau_snr_full_length = self.calculate_tau_snr(time_window) + tau_snr_part_length = self.calculate_tau_snr(time_window_slope_zero) + - P_signal_received = Pe*self.FrequencyExtraction.epsilon_collection*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) - tau_snr = kB*tn_system_fft/P_signal_received - sigma_f_CRLB = np.sqrt((20*(alpha_approx*tau_snr)**2 + 180*tau_snr/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + # use different crlb based on slope + delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+alpha_approx*time_window, self.MagneticField.nominal_field)) + #logger.info("slope is {} Hz/ms".format(alpha_approx/Hz*ms)) + #logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) + if time_window_slope_zero >= time_window: + #logger.info("slope is approximately 0".format(alpha_approx/meV*ms)) + CRLB_constant = np.sqrt(12) + ratio_window_to_length = 1 + #sigma_f_CRLB = gamma(self.T_endpoint)*self.T_endpoint/((gamma(self.T_endpoint)-1)*2*np.pi*endpoint_frequency)*np.sqrt(CRLB_constant*tau_snr*0.3/(time_window**3*ratio_window_to_length**2.3)) + sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/time_window**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor + else: + CRLB_constant = np.sqrt(12) + sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/time_window_slope_zero**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor + + sigma_f_CRLB_slope_fitted = np.sqrt((20*(alpha_approx*tau_snr_full_length)**2 + 180*tau_snr_full_length/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - # end of Wouter's calculation + sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 0b66f0f0..9d53b8f3 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,7 +19,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_262MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_efficiency_curve.pdf", # optional "track_length_axis": False, @@ -36,7 +36,7 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_262MHz_Experiment.cfg", + "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, "B_inhom_uncertainty": 0.01, "lower_label_y_position": 0.17, @@ -50,7 +50,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_262MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "track_length_axis": True, @@ -67,7 +67,7 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_262MHz_Experiment.cfg", + "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, "B_inhom_uncertainty": 0.01, "lower_label_y_position": 0.17, From 727376e141d077dd5d8a77767ed1a02724e8199f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 12 Jun 2023 15:33:01 -0700 Subject: [PATCH 040/262] added hanneke factor and efficiency namespace --- mermithid/misc/SensitivityCavityFormulas.py | 123 ++++++++++++------ .../CavitySensitivityCurveProcessor.py | 22 ++-- tests/Sensitivity_test.py | 15 ++- 3 files changed, 104 insertions(+), 56 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index e2086571..892a219d 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -13,7 +13,7 @@ # Numericalunits is a package to handle units and some natural constants # natural constants from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W from numericalunits import hour, year, day, s, ms from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck @@ -122,7 +122,7 @@ def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, m # An electron will see a slightly different magnetic field # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. - y = (90-max_pitch_angle)/5 + y = (90-max_pitch_angle)/4 return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) # Noise power entering the amplifier, inclding the transmitted noise from the cavity and the reflected noise from the circulator. @@ -218,31 +218,71 @@ def __init__(self, config_path): self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + self.CavityRadius() self.CavityVolume() - + self.EffectiveVolume() + self.CavityPower() # CAVITY def CavityRadius(self): axial_mode_index = 1 - return c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.L_over_D**2)) - + self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.L_over_D**2)) + return self.cavity_radius + def CavityVolume(self): #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) - radius = self.CavityRadius() - self.TotalVolume = 2*radius*self.Experiment.L_over_D*np.pi*(radius)**2 - self.EffectiveVolume = self.TotalVolume*self.Experiment.efficiency + self.total_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2 logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) - logger.info("Radius: {} cm".format(round(radius/cm, 3))) - logger.info("Effective Volume: {} m^3".format(round(self.EffectiveVolume/m**3, 3))) - + logger.info("Radius: {} cm".format(round(self.cavity_radius/cm, 3))) + + return self.total_volume + + def EffectiveVolume(self): + if self.Efficiency.usefixedvalue: + self.effective_volume = self.total_volume * self.Efficiency.total_efficiency + else: + # trapping efficiecny is currently configured. replace with box trap calculation + self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.trapping_efficiency + + return self.effective_volume + + def CavityPower(self): + # from Hamish's atomic calculator + Jprime_0 = 3.8317 + + self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (self.total_volume/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + + return self.signal_power + + + def CavityLoadedQ(self): + # Using Wouter's calculation: + # Total required bandwidth is the sum of the endpoint region and the axial frequency. + # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI + endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + self.T_endpoint, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + + required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, + self.Experiment.L_over_D, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + + required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting + + # Cavity coupling + self.loaded_q = endpoint_frequency/required_bw # FWHM + return self.loaded_q + # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - - signal_rate = self.Experiment.number_density*self.TotalVolume*self.Experiment.efficiency*self.last_1ev_fraction/self.tau_tritium + self.EffectiveVolume() + signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) @@ -417,24 +457,16 @@ def syst_doppler_broadening(self): def calculate_tau_snr(self, time_window): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) - Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) - - # Using Wouter's calculation: - # Total required bandwidth is the sum of the endpoint region and the axial frequency. - # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI - required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint) - required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, length_diameter_ratio=self.Experiment.L_over_D) - required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting # Cavity coupling - loaded_Q = endpoint_frequency/required_bw # FWHM - coupling = self.FrequencyExtraction.unloaded_q/loaded_Q-1 + self.CavityLoadedQ() + coupling = self.FrequencyExtraction.unloaded_q/self.loaded_q-1 # Attenuation frequency dependence at hoc method - att_cir_db = -0.3 - att_line_db = -0.05 - att_cir_db_freq = att_cir_db*(1+endpoint_frequency/(10*GHz)) - att_line_db_freq = att_line_db*(1+endpoint_frequency/(10*GHz)) + #att_cir_db = -0.3 + #att_line_db = -0.05 + att_cir_db_freq = self.FrequencyExtraction.att_cir_db*(1+endpoint_frequency/(10*GHz)) + att_line_db_freq = self.FrequencyExtraction.att_line_db*(1+endpoint_frequency/(10*GHz)) # Noise power for bandwidth set by density/track length fft_bandwidth = 3/time_window #(delta f) is the frequency bandwidth of interest. We have a main carrier and 2 axial side bands, so 3*(FFT bin width) @@ -443,13 +475,17 @@ def calculate_tau_snr(self, time_window): att_line_db_freq,att_cir_db_freq, coupling, endpoint_frequency, - fft_bandwidth,loaded_Q)/kB/fft_bandwidth + fft_bandwidth,self.loaded_q)/kB/fft_bandwidth # Noise temperature of amplifier tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency tn_system_fft = tn_amplifier+tn_fft - P_signal_received = Pe*self.FrequencyExtraction.epsilon_collection*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) + # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) + # logger.info("Power: {}".format(Pe/W)) + Pe = self.signal_power + + P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) tau_snr = kB*tn_system_fft/P_signal_received # end of Wouter's calculation @@ -488,11 +524,13 @@ def syst_frequency_extraction(self): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) # using Pe and alpha (aka slope) from above - Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) - alpha_approx = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope + Pe = self.CavityPower()/self.FrequencyExtraction.mode_coupling_efficiency + self.larmor_power = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # currently not used + + self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/alpha_approx + time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+10*meV, self.MagneticField.nominal_field))/self.slope tau_snr_full_length = self.calculate_tau_snr(time_window) tau_snr_part_length = self.calculate_tau_snr(time_window_slope_zero) @@ -500,22 +538,27 @@ def syst_frequency_extraction(self): # use different crlb based on slope - delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+alpha_approx*time_window, self.MagneticField.nominal_field)) - #logger.info("slope is {} Hz/ms".format(alpha_approx/Hz*ms)) - #logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) + delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+self.slope*time_window, self.MagneticField.nominal_field)) + logger.info("slope is {} Hz/ms".format(self.slope/Hz*ms)) + # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) if time_window_slope_zero >= time_window: - #logger.info("slope is approximately 0".format(alpha_approx/meV*ms)) + #logger.info("slope is approximately 0".format(self.slope/meV*ms)) CRLB_constant = np.sqrt(12) ratio_window_to_length = 1 #sigma_f_CRLB = gamma(self.T_endpoint)*self.T_endpoint/((gamma(self.T_endpoint)-1)*2*np.pi*endpoint_frequency)*np.sqrt(CRLB_constant*tau_snr*0.3/(time_window**3*ratio_window_to_length**2.3)) sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/time_window**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor + self.slope_is_zero=True else: CRLB_constant = np.sqrt(12) sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/time_window_slope_zero**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(alpha_approx*tau_snr_full_length)**2 + 180*tau_snr_full_length/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) + + # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) + self.slope_is_zero = False + self.crlb_decision=np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) @@ -529,8 +572,10 @@ def syst_frequency_extraction(self): # combined sigma_f in eV sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) - - return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f + if self.FrequencyExtraction.usefixeduncertainty: + return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f + else: + raise NotImplementedError("Unvertainty on CRLB for cavity noise calculation is not implemented.") def syst_magnetic_field(self): diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 45bfdab1..3f0e2def 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -98,7 +98,7 @@ def InternalConfigure(self, params): # setup sensitivities - self.cavity = reader.read_param(params, 'cavity', False) + self.cavity = reader.read_param(params, 'cavity', True) if self.cavity: self.sens_main = CavitySensitivity(self.config_file_path) @@ -137,7 +137,7 @@ def InternalConfigure(self, params): # densities self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 self.effs = np.logspace(np.log10(self.efficiency_range[0]), np.log10(self.efficiency_range[1]), 10) - self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 10)/year + self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 10)*year return True @@ -202,14 +202,16 @@ def InternalRun(self): - logger.info('Main curve (veff = {} m**3, rho = {} /m**3):'.format(self.sens_main.EffectiveVolume/(m**3), rho_opt*(m**3))) + logger.info('Main curve:') + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.EffectiveVolume)) - logger.info('Total signal: {}'.format(rho_opt*self.sens_main.EffectiveVolume* + logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) + logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* - rho_opt*self.sens_main.EffectiveVolume* + rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) @@ -303,12 +305,12 @@ def add_comparison_curve(self, label, color='k'): rho_opt = self.rhos[opt_ref] logger.info('Ref. optimum density: {} /m**3'.format(rho_opt*m**3)) - logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.EffectiveVolume/(m**3))) - logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.EffectiveVolume)) - logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.EffectiveVolume* + logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.effective_volume/(m**3))) + logger.info('Ref. T in Veff: {}'.format(rho_opt*self.sens_ref.effective_volume)) + logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.effective_volume* self.sens_ref.Experiment.LiveTime/ self.sens_ref.tau_tritium)) - logger.info('CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) limits = self.add_sens_line(self.sens_ref, color=color) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 9d53b8f3..6e8fdfba 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -27,7 +27,7 @@ def test_SensitivityCurveProcessor(self): "atomic_axis": False, "density_axis": False, "cavity": True, - "y_limits": [2e-2, 3], + "y_limits": [2e-2, 4], "density_range": [1e12,1e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], @@ -44,9 +44,9 @@ def test_SensitivityCurveProcessor(self): "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.0002 #2e12 #0.0002 } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { # required @@ -58,8 +58,8 @@ def test_SensitivityCurveProcessor(self): "atomic_axis": True, "density_axis": True, "cavity": True, - "y_limits": [2e-2, 3], - "density_range": [1e12,1e18], + "y_limits": [2e-2, 4], + "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular"+"\n"+"1 year"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", @@ -73,7 +73,8 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 1.5e15, #0.02, #1e14, - "goals_x_position": 2e12 #0.0002 + "goals_x_position": 2e12, #0.0002 + "plot_key_parameters": True } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From edfe3855ccfd631f1eb52003c7be2cc8d96cc5f2 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 13 Jun 2023 10:48:44 -0400 Subject: [PATCH 041/262] Added temporary alternative termite path option --- tests/Sensitivity_test.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 9d53b8f3..ad6eb55e 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,7 +19,8 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_efficiency_curve.pdf", # optional "track_length_axis": False, @@ -36,7 +37,8 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, "B_inhom_uncertainty": 0.01, "lower_label_y_position": 0.17, @@ -50,7 +52,8 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "track_length_axis": True, @@ -67,7 +70,8 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, "B_inhom_uncertainty": 0.01, "lower_label_y_position": 0.17, @@ -85,7 +89,8 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } # sens = AnalyticSensitivityEstimation("sensitivity_processor") # sens.Configure(sens_config_dict) @@ -103,7 +108,8 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required - "config_file_path": "//host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + #"config_file_path": "//host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "sensitivity_target": [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] } #sens = ConstantSensitivityParameterPlots("sensitivity_processor") From f59dad1b098c7f6d6d2bfb1c2d12ea0c3f16f82a Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 13 Jun 2023 20:00:07 -0400 Subject: [PATCH 042/262] Use more precise value for getting limit from sigma mb^2 --- .../Sensitivity/AnalyticSensitivityEstimation.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py index 7d5011c8..650ff09d 100644 --- a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -104,11 +104,13 @@ def sensitivity(self, **kwargs): def CL90(self, **kwargs): """ Gives 90% CL upper limit on neutrino mass.""" - # 90% of gaussian are contained in +-1.64 sigma region - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + # If integrate gaussian -infinity to 1.28*sigma, the result is 90%. + # https://www.wolframalpha.com/input?i=1%2F2+%281+%2B+erf%281.281551%2Fsqrt%282%29%29%29 + # So we assume that 1.28*sigma_{m_beta^2} is the 90% upper limit on m_beta^2. + return np.sqrt(1.281551*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + return np.sqrt(1.281551*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # print functions From fb88e5b8efd69cea818f21dd815aa9c4999cb031 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 13 Jun 2023 21:55:40 -0400 Subject: [PATCH 043/262] Updated cross-sections for T and T2 --- mermithid/misc/SensitivityCavityFormulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 892a219d..1bee54a2 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -22,12 +22,12 @@ tritium_livetime = 5.605e8*s tritium_mass_atomic = 3.016* amu *c0**2 -tritium_electron_crosssection_atomic = 1.1e-22*m**2 +tritium_electron_crosssection_atomic = 9.e-23*m**2 #From Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 tritium_endpoint_atomic = 18563.251*eV last_1ev_fraction_atomic = 2.067914e-13/eV**3 tritium_mass_molecular = 6.032099 * amu *c0**2 -tritium_electron_crosssection_molecular = 3.487*1e-22*m**2 +tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 #[Inelastic from Aseev (2000) for T2] + [Elastic from Liu (1987) for H2, extrapolated by Elise to 18.6keV] tritium_endpoint_molecular = 18573.24*eV last_1ev_fraction_molecular = 1.67364e-13/eV**3 From 778a9350f9d5bada1afba8b60ef10a16977bf154 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 13 Jun 2023 22:13:16 -0700 Subject: [PATCH 044/262] added key parameter plot option. disabled crlb decision. added 2 pi in power calculation. --- mermithid/misc/SensitivityCavityFormulas.py | 39 +++++++------ .../CavitySensitivityCurveProcessor.py | 56 +++++++++++++++++-- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 892a219d..d704e493 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -32,7 +32,7 @@ last_1ev_fraction_molecular = 1.67364e-13/eV**3 ground_state_width = 0.436 * eV -ground_state_width_uncertainty = 0.0*0.436*eV +ground_state_width_uncertainty = 0.001*0.436*eV gyro_mag_ratio_proton = 42.577*MHz/T @@ -243,7 +243,7 @@ def CavityVolume(self): def EffectiveVolume(self): if self.Efficiency.usefixedvalue: - self.effective_volume = self.total_volume * self.Efficiency.total_efficiency + self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency else: # trapping efficiecny is currently configured. replace with box trap calculation self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.trapping_efficiency @@ -254,7 +254,7 @@ def CavityPower(self): # from Hamish's atomic calculator Jprime_0 = 3.8317 - self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (self.total_volume/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W return self.signal_power @@ -524,41 +524,40 @@ def syst_frequency_extraction(self): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) # using Pe and alpha (aka slope) from above - Pe = self.CavityPower()/self.FrequencyExtraction.mode_coupling_efficiency + Pe = self.CavityPower() #/self.FrequencyExtraction.mode_coupling_efficiency self.larmor_power = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # currently not used self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope - time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) + self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+10*meV, self.MagneticField.nominal_field))/self.slope + self.time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/self.slope - tau_snr_full_length = self.calculate_tau_snr(time_window) - tau_snr_part_length = self.calculate_tau_snr(time_window_slope_zero) + tau_snr_full_length = self.calculate_tau_snr(self.time_window) + tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero) # use different crlb based on slope - delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+self.slope*time_window, self.MagneticField.nominal_field)) - logger.info("slope is {} Hz/ms".format(self.slope/Hz*ms)) + # delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+self.slope*ms, self.MagneticField.nominal_field)) + # logger.info("slope is {} Hz/ms".format(self.slope/Hz*ms)) # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) - if time_window_slope_zero >= time_window: - #logger.info("slope is approximately 0".format(self.slope/meV*ms)) + if True: #self.time_window_slope_zero >= self.time_window: + # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) CRLB_constant = np.sqrt(12) - ratio_window_to_length = 1 - #sigma_f_CRLB = gamma(self.T_endpoint)*self.T_endpoint/((gamma(self.T_endpoint)-1)*2*np.pi*endpoint_frequency)*np.sqrt(CRLB_constant*tau_snr*0.3/(time_window**3*ratio_window_to_length**2.3)) - sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/time_window**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.slope_is_zero=True + self.best_time_window = self.time_window else: CRLB_constant = np.sqrt(12) - sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/time_window_slope_zero**3))/(2*np.pi)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) self.slope_is_zero = False - self.crlb_decision=np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) + self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])] """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) @@ -566,11 +565,11 @@ def syst_frequency_extraction(self): delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" # sigma_f from Cramer-Rao lower bound in eV - sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 + self.sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*delta_sigma_f_CRLB*c0**2 # combined sigma_f in eV - sigma_f = np.sqrt(sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + sigma_f = np.sqrt(self.sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) if self.FrequencyExtraction.usefixeduncertainty: return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 3f0e2def..8e504663 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -18,9 +18,9 @@ # Numericalunits is a package to handle units and some natural constants # natural constants from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz +from numericalunits import meV, eV, keV, MeV, cm, m from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W -from numericalunits import hour, year, day +from numericalunits import hour, year, day, ms, ns, s, Hz, kHz, MHz, GHz ppm = 1e-6 ppb = 1e-9 @@ -70,7 +70,7 @@ def InternalConfigure(self, params): self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) - # plot configurations + # main plot configurations self.figsize = reader.read_param(params, 'figsize', (6,6)) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.efficiency_range = reader.read_param(params, 'efficiency_range', [0.001,0.1]) @@ -85,6 +85,9 @@ def InternalConfigure(self, params): self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + # other plot + self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) + if self.density_axis: self.add_sens_line = self.add_density_sens_line logger.info("Doing density lines") @@ -190,6 +193,7 @@ def InternalRun(self): # save plot self.save(self.plot_path) + # PRINT OPTIMUM RESULTS # print number of events @@ -205,6 +209,7 @@ def InternalRun(self): logger.info('Main curve:') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) + logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -250,6 +255,21 @@ def create_plot(self): ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + + if self.make_key_parameter_plots: + self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,5)) + self.kp_ax[0].set_ylabel('Resolution (meV)') + self.kp_ax[1].set_ylabel('Track analysis length (ms)') + + if self.density_axis: + + for ax in self.kp_ax: + ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) + ax.set_xscale("log") + ax.set_yscale("log") + axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" + ax.set_xlabel(axis_label) + def add_track_length_axis(self): if self.atomic_axis: @@ -306,13 +326,14 @@ def add_comparison_curve(self, label, color='k'): logger.info('Ref. optimum density: {} /m**3'.format(rho_opt*m**3)) logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.effective_volume/(m**3))) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref.larmor_power/W, self.sens_ref.signal_power/W)) logger.info('Ref. T in Veff: {}'.format(rho_opt*self.sens_ref.effective_volume)) logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.effective_volume* self.sens_ref.Experiment.LiveTime/ self.sens_ref.tau_tritium)) logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) - limits = self.add_sens_line(self.sens_ref, color=color) + limits = self.add_sens_line(self.sens_ref, plot_key_params=True, color=color) #self.ax.axhline(0.04, color="gray", ls="--") #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") @@ -349,10 +370,30 @@ def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") self.ax.text(self.goal_x_pos, 0.75*value/eV, label) - def add_density_sens_line(self, sens, **kwargs): - limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): + limits = [] + resolutions = [] + crlb_window = [] + crlb_max_window = [] + crlb_slope_zero_window = [] + + for rho in self.rhos: + limits.append(sens.CL90(Experiment={"number_density": rho})/eV) + resolutions.append(sens.sigma_K_f_CRLB/meV) + crlb_window.append(sens.best_time_window/ms) + crlb_max_window.append(sens.time_window/ms) + crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) + + self.ax.plot(self.rhos*m**3, limits, **kwargs) logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) + + if self.make_key_parameter_plots and plot_key_params: + self.kp_ax[0].plot(self.rhos*m**3, resolutions, **kwargs) + + self.kp_ax[1].plot(self.rhos*m**3, crlb_max_window, color='red', marker='.') + self.kp_ax[1].plot(self.rhos*m**3, crlb_slope_zero_window, color='green', marker='.') + self.kp_ax[1].plot(self.rhos*m**3, crlb_window, linestyle="--", marker='.', **kwargs) return limits def add_exposure_sens_line(self, sens, **kwargs): @@ -387,5 +428,8 @@ def save(self, savepath, **kwargs): if savepath is not None: self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=300, metadata=metadata) self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) + + if self.make_key_parameter_plots: + self.kp_fig.savefig('key_parameters.pdf') From fda04a6dc3427f95fd5f36868fff420ebb716c67 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 16:31:15 -0400 Subject: [PATCH 045/262] Broke down sigma_b; updated plots --- mermithid/misc/SensitivityCavityFormulas.py | 16 +++++-- .../CavitySensitivityCurveProcessor.py | 28 +++++++---- tests/Sensitivity_test.py | 48 ++++++++++--------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 1bee54a2..1d264110 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -22,7 +22,7 @@ tritium_livetime = 5.605e8*s tritium_mass_atomic = 3.016* amu *c0**2 -tritium_electron_crosssection_atomic = 9.e-23*m**2 #From Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 +tritium_electron_crosssection_atomic = 9.e-23*m**2 #Hamish extrapolated to 18.6keV using Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 tritium_endpoint_atomic = 18563.251*eV last_1ev_fraction_atomic = 2.067914e-13/eV**3 @@ -595,10 +595,15 @@ def syst_magnetic_field(self): B = self.MagneticField.nominal_field if self.MagneticField.useinhomogenarity: - inhomogenarity = self.MagneticField.inhomogenarity - sigma = self.BToKeErr(inhomogenarity*B, B) - return sigma, 0.05*sigma - + frac_uncertainty = self.MagneticField.fraction_uncertainty_on_field_broadening + sigma_meanB = self.MagneticField.sigma_meanb + sigmaE_meanB = self.BToKeErr(sigma_meanB*B, B) + sigmaE_r = self.MagneticField.sigmae_r + sigmaE_theta = self.MagneticField.sigmae_theta + sigmaE_phi = self.MagneticField.sigmae_theta + sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r**2 + sigmaE_theta**2 + sigmaE_phi**2) + return sigma, frac_uncertainty*sigma + """ BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution @@ -645,6 +650,7 @@ def syst_magnetic_field(self): rProbePhiErr**2 * delta_rProbePhiErr**2) return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) + """ def syst_missing_tracks(self): # this systematic should describe the energy broadening due to the line shape. diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 3f0e2def..cf98f169 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -68,6 +68,7 @@ def InternalConfigure(self, params): self.comparison_curve = reader.read_param(params, 'comparison_curve', False) self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) + self.sigmae_theta_r = reader.read_param(params, 'sigmae_theta_r', np.array([0.03])) #eV # plot configurations @@ -167,22 +168,28 @@ def InternalRun(self): self.sens_main.Experiment.number_density = rho_opt # if B is list plot line for each B - if isinstance(self.B_error, list) or isinstance(self.B_error, np.ndarray): - N = len(self.B_error) + if isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray): + N = len(self.sigmae_theta_r) for a, color in self.range(0, N): - sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) - self.sens_main.MagneticField.usefixedvalue = True - self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) + #self.sens_main.MagneticField.usefixedvalue = True + #self.sens_main.MagneticField.default_systematic_smearing = sig + #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[a] * eV + self.sens_main.MagneticField.sigmae_theta = 0 * eV self.add_sens_line(self.sens_main, color=color) + #print("sigmae_theta_r:", self.sens_main.MagneticField.sigmae_r/eV) + self.sens_main.print_systematics() self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color="darkblue") self.add_text(self.label_x_position, self.lower_label_y_position, self.main_curve_lower_label, color="darkred") else: - sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) - self.sens_main.MagneticField.usefixedvalue = True - self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) + #self.sens_main.MagneticField.usefixedvalue = True + #self.sens_main.MagneticField.default_systematic_smearing = sig + #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV + self.sens_main.MagneticField.sigmae_theta = 0 * eV self.add_sens_line(self.sens_main, color='blue') self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label) @@ -305,6 +312,7 @@ def add_comparison_curve(self, label, color='k'): rho_opt = self.rhos[opt_ref] logger.info('Ref. optimum density: {} /m**3'.format(rho_opt*m**3)) + logger.info('Ref. sigmaE_r: {} eV'.format(self.sens_ref.MagneticField.sigmae_r/eV)) logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.effective_volume/(m**3))) logger.info('Ref. T in Veff: {}'.format(rho_opt*self.sens_ref.effective_volume)) logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.effective_volume* diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 2fad28e3..42d0f803 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,8 +19,8 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_efficiency_curve.pdf", # optional "track_length_axis": False, @@ -32,28 +32,29 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"1 year"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", - "main_curve_lower_label": r"$\sigma_B = 0.1\,\mathrm{ppm}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, - "B_inhom_uncertainty": 0.01, + #"comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmaE_theta_r": np.linspace(0.07, 0.15, 10), #in eV, energy broadening from theta and r reconstruction "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.0002 #2e12 #0.0002 } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "track_length_axis": True, @@ -65,15 +66,16 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"1 year"+"\n"+r"$\sigma_B = 2\,\mathrm{ppm}$", - "main_curve_lower_label": r"$\sigma_B = 0.1\,\mathrm{ppm}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma_B = 0.1\,\mathrm{ppm}$", + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, - "B_inhom_uncertainty": 0.01, + #"comparison_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": np.linspace(0.07, 1., 10), #in eV, energy broadening from theta and r reconstruction "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 1.5e15, #0.02, #1e14, @@ -90,8 +92,8 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } # sens = AnalyticSensitivityEstimation("sensitivity_processor") # sens.Configure(sens_config_dict) From 45068b90dad4a37d732f82e2c9a466f474df954e Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 17:40:42 -0400 Subject: [PATCH 046/262] Changed calc of T2 thermal broadening delta-sig, printed total sigma --- mermithid/misc/SensitivityCavityFormulas.py | 9 ++++- .../CavitySensitivityCurveProcessor.py | 16 +++++--- tests/Sensitivity_test.py | 38 +++++++++---------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index a0f34a2a..2e5077b0 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -418,8 +418,12 @@ def print_systematics(self): labels, sigmas, deltas = self.get_systematics() print() + sigma_squared = 0 for label, sigma, delta in zip(labels, sigmas, deltas): print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") + sigma_squared += sigma**2 + sigma_total = np.sqrt(sigma_squared) + print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from @@ -451,7 +455,10 @@ def syst_doppler_broadening(self): p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) - delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + if self.Experiment.atomic == True: + delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + else: + delta_trans = sigma_trans*self.DopplerBroadening.fraction_uncertainty_on_doppler_broadening return sigma_trans, delta_trans def calculate_tau_snr(self, time_window): diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index f7e890b9..e890e41a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -205,13 +205,17 @@ def InternalRun(self): # print number of events # for minimum field smearing - sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*np.min(self.B_error), self.sens_main.MagneticField.nominal_field) - self.sens_main.MagneticField.usefixedvalue = True - self.sens_main.MagneticField.default_systematic_smearing = sig - self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig + #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*np.min(self.B_error), self.sens_main.MagneticField.nominal_field) + #self.sens_main.MagneticField.usefixedvalue = True + #self.sens_main.MagneticField.default_systematic_smearing = sig + #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig - - + if isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray): + self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[0] * eV + self.sens_main.MagneticField.sigmae_theta = 0 * eV + else: + self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV + self.sens_main.MagneticField.sigmae_theta = 0 * eV logger.info('Main curve:') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 42d0f803..d6583c3c 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,8 +19,8 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_efficiency_curve.pdf", # optional "track_length_axis": False, @@ -32,13 +32,13 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", - "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - #"comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, "sigmaE_theta_r": np.linspace(0.07, 0.15, 10), #in eV, energy broadening from theta and r reconstruction @@ -47,14 +47,14 @@ def test_SensitivityCurveProcessor(self): "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.0002 #2e12 #0.0002 } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { # required - #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "track_length_axis": True, @@ -66,13 +66,13 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 70\,\mathrm{meV}$", - "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (40 meV)": 0.04}, + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - #"comparison_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, "sigmae_theta_r": np.linspace(0.07, 1., 10), #in eV, energy broadening from theta and r reconstruction @@ -92,8 +92,8 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - #"config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - "config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } # sens = AnalyticSensitivityEstimation("sensitivity_processor") # sens.Configure(sens_config_dict) From 6dd4fc5f825604cd8528ddb4c7941b5e43ef8c46 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 19:53:55 -0400 Subject: [PATCH 047/262] Added SRI factor; added second config; print atomic results --- mermithid/misc/SensitivityCavityFormulas.py | 4 +- .../CavitySensitivityCurveProcessor.py | 35 ++++++++++--- tests/Sensitivity_test.py | 49 ++++++++++++++++--- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 2e5077b0..c2d5cb2c 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -1,6 +1,6 @@ ''' Class calculating neutrino mass sensitivities based on analytic formulas from CDR. -Author: R. Reimann, C. Claessens +Author: R. Reimann, C. Claessens, T. Weiss Date:11/17/2020 The statistical method and formulars are described in @@ -282,7 +282,7 @@ def CavityLoadedQ(self): def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" self.EffectiveVolume() - signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium + signal_rate = self.Experiment.number_density*self.effective_volume*self.Experiment.sri_factor*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index e890e41a..6770cda2 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -1,7 +1,7 @@ ''' Calculate sensitivity curve and plot vs. pressure function. -Author: R. Reimann, C. Claessens +Author: R. Reimann, C. Claessens, T. Weiss Date:11/17/2020 More description @@ -68,7 +68,7 @@ def InternalConfigure(self, params): self.comparison_curve = reader.read_param(params, 'comparison_curve', False) self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) - self.sigmae_theta_r = reader.read_param(params, 'sigmae_theta_r', np.array([0.03])) #eV + self.sigmae_theta_r = reader.read_param(params, 'sigmae_theta_r', 0.159) #eV # main plot configurations @@ -166,8 +166,8 @@ def InternalRun(self): # first optimize density limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit) - rho_opt = self.rhos[opt_ref] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] self.sens_main.Experiment.number_density = rho_opt # if B is list plot line for each B @@ -217,7 +217,7 @@ def InternalRun(self): self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - logger.info('Main curve:') + logger.info('Main curve (molecular):') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) @@ -234,6 +234,29 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() + # Optimize atomic density + limit_ref = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit_ref) + rho_opt_ref = self.rhos[opt_ref] + self.sens_ref.Experiment.number_density = rho_opt_ref + + logger.info('Comparison curve (atomic):') + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref.effective_volume/(m**3), rho_opt_ref*(m**3))) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref.larmor_power/W, self.sens_ref.signal_power/W)) + logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref.signal_power/self.sens_ref.larmor_power)) + logger.info('CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt_ref})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref.effective_volume)) + logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref.effective_volume* + self.sens_ref.Experiment.LiveTime/ + self.sens_ref.tau_tritium*2)) + logger.info('Signal in last eV: {}'.format(self.sens_ref.last_1ev_fraction*eV**3* + rho_opt_ref*self.sens_ref.effective_volume* + self.sens_ref.Experiment.LiveTime/ + self.sens_ref.tau_tritium*2)) + + self.sens_ref.print_statistics() + self.sens_ref.print_systematics() + return True @@ -349,7 +372,7 @@ def add_comparison_curve(self, label, color='k'): #self.ax.axhline(0.04, color="gray", ls="--") #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") - self.ax.text(self.label_x_position, 0.042, label) + self.ax.text(self.label_x_position, 0.044, label) def add_arrow(self, sens): if not hasattr(self, "opt_ref"): diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index d6583c3c..c3267438 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -1,6 +1,6 @@ """ Script to test the Sensitivty processors -Author: C. Claessens +Author: C. Claessens, T. Weiss Date: December 12, 2021 """ @@ -33,7 +33,7 @@ def test_SensitivityCurveProcessor(self): "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, @@ -51,6 +51,43 @@ def test_SensitivityCurveProcessor(self): #sens_curve.Configure(sens_config_dict) #sens_curve.Run() + + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_curve_with_sigmaBcorrs.pdf", + # optional + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,1e19], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", + "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 1.5e15, #0.02, #1e14, + "goals_x_position": 2e12, #0.0002 + "plot_key_parameters": True + } + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() + + sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", @@ -66,16 +103,16 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 1\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.07\,\mathrm{eV}$", + "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, - "sigmae_theta_r": np.linspace(0.07, 1., 10), #in eV, energy broadening from theta and r reconstruction + "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 1.5e15, #0.02, #1e14, From 672902a42b86ce87b20aaa30f96c362ddbe57e76 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 21:02:56 -0400 Subject: [PATCH 048/262] Enable multiple comparison curves with diff config files --- .../CavitySensitivityCurveProcessor.py | 100 ++++++++++-------- tests/Sensitivity_test.py | 14 ++- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 6770cda2..967da766 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -82,6 +82,7 @@ def InternalConfigure(self, params): self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) + self.comparison_label_y_position = reader.read_param(params, 'comparison_label_y_position', 0.044) self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) @@ -111,30 +112,37 @@ def InternalConfigure(self, params): self.sens_main_is_atomic = self.sens_main.Experiment.atomic if self.comparison_curve: + ref = [] if self.cavity: - self.sens_ref = CavitySensitivity(self.comparison_config_file_path) + for file in self.comparison_config_file_path: + ref.append(CavitySensitivity(file)) else: - self.sens_ref = Sensitivity(self.comparison_config_file_path) - self.sens_ref_is_atomic = self.sens_ref.Experiment.atomic + for file in self.comparison_config_file_path: + ref.append(CavitySensitivity(file)) + self.sens_ref = ref + is_atomic = [] + for i in range(len(self.sens_ref)): + is_atomic.append(self.sens_ref[i].Experiment.atomic) + self.sens_ref_is_atomic = is_atomic # check atomic and molecular if self.molecular_axis: if not self.sens_main_is_atomic: - self.molecular_sens = self.sens_main + #self.molecular_sens = self.sens_main logger.info("Main curve is molecular") - elif not self.sens_ref_is_atomic: - self.molecular_sens = self.sens_ref - logger.info("Comparison curve is molecular") - else: + elif False not in self.sens_ref_is_atomic: raise ValueError("No experiment is configured to be molecular") + #self.molecular_sens = self.sens_ref + #logger.info("Comparison curve is molecular") + #else: if self.atomic_axis: if self.sens_main_is_atomic: - self.atomic_sens = self.sens_main + #self.atomic_sens = self.sens_main logger.info("Main curve is atomic") - elif self.sens_ref_is_atomic: - self.atomic_sens = self.sens_ref - logger.info("Comparison curve is atomic") + elif True in self.sens_ref_is_atomic: + #self.atomic_sens = self.sens_ref + logger.info("A comparison curve is atomic") else: raise ValueError("No experiment is configured to be atomic") @@ -235,27 +243,28 @@ def InternalRun(self): self.sens_main.print_systematics() # Optimize atomic density - limit_ref = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit_ref) - rho_opt_ref = self.rhos[opt_ref] - self.sens_ref.Experiment.number_density = rho_opt_ref - - logger.info('Comparison curve (atomic):') - logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref.effective_volume/(m**3), rho_opt_ref*(m**3))) - logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref.larmor_power/W, self.sens_ref.signal_power/W)) - logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref.signal_power/self.sens_ref.larmor_power)) - logger.info('CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt_ref})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref.effective_volume)) - logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref.effective_volume* - self.sens_ref.Experiment.LiveTime/ - self.sens_ref.tau_tritium*2)) - logger.info('Signal in last eV: {}'.format(self.sens_ref.last_1ev_fraction*eV**3* - rho_opt_ref*self.sens_ref.effective_volume* - self.sens_ref.Experiment.LiveTime/ - self.sens_ref.tau_tritium*2)) - - self.sens_ref.print_statistics() - self.sens_ref.print_systematics() + for i in range(len(self.sens_ref)): + limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit_ref) + rho_opt_ref = self.rhos[opt_ref] + self.sens_ref[i].Experiment.number_density = rho_opt_ref + + logger.info('Comparison curve:') + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) + logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) + logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) + logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* + self.sens_ref[i].Experiment.LiveTime/ + self.sens_ref[i].tau_tritium*2)) + logger.info('Signal in last eV: {}'.format(self.sens_ref[i].last_1ev_fraction*eV**3* + rho_opt_ref*self.sens_ref[i].effective_volume* + self.sens_ref[i].Experiment.LiveTime/ + self.sens_ref[i].tau_tritium*2)) + + self.sens_ref[i].print_statistics() + self.sens_ref[i].print_systematics() return True @@ -306,12 +315,13 @@ def create_plot(self): def add_track_length_axis(self): + N_ref = len(self.sens_ref) if self.atomic_axis: ax2 = self.ax.twiny() ax2.set_xscale("log") ax2.set_xlabel("(Atomic) track length (s)") - ax2.set_xlim(self.atomic_sens.track_length(self.rhos[0])/s, - self.atomic_sens.track_length(self.rhos[-1])/s) + ax2.set_xlim(self.sens_ref[N_ref - 1].track_length(self.rhos[0])/s, + self.sens_ref[N_ref - 1].track_length(self.rhos[-1])/s) if self.molecular_axis: ax3 = self.ax.twiny() @@ -326,8 +336,8 @@ def add_track_length_axis(self): ax3.set_xscale("log") ax3.set_xlabel("(Molecular) track length (s)") - ax3.set_xlim(self.molecular_sens.track_length(self.rhos[0])/s, - self.molecular_sens.track_length(self.rhos[-1])/s) + ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) else: logger.warning("No track length axis added since neither atomic nor molecular was requested") @@ -353,7 +363,7 @@ def add_track_length_axis(self): def add_comparison_curve(self, label, color='k'): - + """ limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] opt_ref = np.argmin(limit) rho_opt = self.rhos[opt_ref] @@ -367,12 +377,14 @@ def add_comparison_curve(self, label, color='k'): self.sens_ref.Experiment.LiveTime/ self.sens_ref.tau_tritium)) logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) - - limits = self.add_sens_line(self.sens_ref, plot_key_params=True, color=color) + """ + for a, color in self.range(0, len(self.sens_ref)): + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=color) + self.ax.text(self.label_x_position, self.comparison_label_y_position[a], label[a]) + #self.ax.axhline(0.04, color="gray", ls="--") #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") - self.ax.text(self.label_x_position, 0.044, label) def add_arrow(self, sens): if not hasattr(self, "opt_ref"): @@ -381,7 +393,8 @@ def add_arrow(self, sens): def get_relative(val, axis): xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() return (np.log10(val)-np.log10(xmin))/(np.log10(xmax)-np.log10(xmin)) - + + """ rho_IV = self.rhos[self.opt_ref] track_length_IV = self.sens_ref.track_length(rho_IV) track_length_III = self.sens_main.track_length(rho_IV) @@ -400,7 +413,8 @@ def get_relative(val, axis): head_width=0.02, head_length=0.03, ) - + """ + def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") self.ax.text(self.goal_x_pos, 0.75*value/eV, label) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index c3267438..5f08d08a 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -69,14 +69,15 @@ def test_SensitivityCurveProcessor(self): #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction + "comparison_label_y_position": [0.044], "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 1.5e15, #0.02, #1e14, @@ -104,14 +105,11 @@ def test_SensitivityCurveProcessor(self): "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", - "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.07\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, - #"B_inhom_uncertainty": 0.01, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [0.044], "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, From 8ad1eff731ddee68fba797566abc18596cd15a4a Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 14 Jun 2023 19:37:18 -0700 Subject: [PATCH 049/262] added sideband power fraction in crlb calculation --- mermithid/misc/SensitivityCavityFormulas.py | 3 +++ ...tivity_vs_density_curve_with_sigmaBcorrs.pdf | Bin 0 -> 30695 bytes 2 files changed, 3 insertions(+) create mode 100644 tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index c2d5cb2c..978d8ef0 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -492,6 +492,9 @@ def calculate_tau_snr(self, time_window): # logger.info("Power: {}".format(Pe/W)) Pe = self.signal_power + if self.FrequencyExtraction.crlb_on_sidebands: + Pe*=self.FrequencyExtraction.sideband_power_fraction + P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) tau_snr = kB*tn_system_fft/P_signal_received diff --git a/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf b/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7866abec64c7040c644566bbb8fcc89efcb8a8f5 GIT binary patch literal 30695 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 Date: Wed, 14 Jun 2023 22:44:11 -0400 Subject: [PATCH 050/262] Added second config; option for x labels in diff positions --- .../CavitySensitivityCurveProcessor.py | 7 +++++-- tests/Sensitivity_test.py | 17 +++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 967da766..673e3bc0 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -83,6 +83,9 @@ def InternalConfigure(self, params): self.molecular_axis = reader.read_param(params, 'molecular_axis', False) self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) self.comparison_label_y_position = reader.read_param(params, 'comparison_label_y_position', 0.044) + self.comparison_label_x_position = reader.read_param(params, 'comparison_label_x_position', 5e16) + if self.comparison_label_x_position == None: + self.comparison_label_x_position = reader.read_param(params, 'label_x_position', 5e16) self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) @@ -380,7 +383,7 @@ def add_comparison_curve(self, label, color='k'): """ for a, color in self.range(0, len(self.sens_ref)): limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=color) - self.ax.text(self.label_x_position, self.comparison_label_y_position[a], label[a]) + self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a]) #self.ax.axhline(0.04, color="gray", ls="--") @@ -414,7 +417,7 @@ def get_relative(val, axis): head_length=0.03, ) """ - + def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") self.ax.text(self.goal_x_pos, 0.75*value/eV, label) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 5f08d08a..eebd4bdb 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -84,9 +84,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -104,16 +104,17 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", - "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], + "main_curve_upper_label": r"Molecular, 2 years", + "comparison_curve_label": ["Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [0.044], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [0.15, 0.044], + "comparison_label_x_position": [3e16, 1e15], "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, - "label_x_position": 1.5e15, #0.02, #1e14, + "label_x_position": 6e14, #0.02, #1e14, "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } From 60fa3f5eab6b0a092a70c2d3d62945a25d6cc708 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 22:54:19 -0400 Subject: [PATCH 051/262] Now using config with CRLB on sidebands --- tests/Sensitivity_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index eebd4bdb..3e834223 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -109,7 +109,7 @@ def test_SensitivityCurveProcessor(self): "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [0.15, 0.044], + "comparison_label_y_position": [0.09, 0.044], "comparison_label_x_position": [3e16, 1e15], "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, From bffd33a80018da552597aca2b36f768b5430747f Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 14 Jun 2023 23:55:03 -0400 Subject: [PATCH 052/262] Formatting for sensitivity vs. density plot --- .../CavitySensitivityCurveProcessor.py | 17 +++++++++-------- tests/Sensitivity_test.py | 12 ++++++------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 673e3bc0..ca4a8bcc 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -205,7 +205,7 @@ def InternalRun(self): self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV self.add_sens_line(self.sens_main, color='blue') - self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label) + self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') # save plot @@ -381,9 +381,10 @@ def add_comparison_curve(self, label, color='k'): self.sens_ref.tau_tritium)) logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) """ - for a, color in self.range(0, len(self.sens_ref)): - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=color) - self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a]) + colors = ["darkblue", "darkred", "red"] + for a in range(len(self.sens_ref)): + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=colors[a]) + self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) #self.ax.axhline(0.04, color="gray", ls="--") @@ -420,7 +421,7 @@ def get_relative(val, axis): def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") - self.ax.text(self.goal_x_pos, 0.75*value/eV, label) + self.ax.text(self.goal_x_pos, 0.8*value/eV, label) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -461,12 +462,12 @@ def add_exposure_sens_line(self, sens, **kwargs): print(limits) self.ax.plot(self.effs, limits, **kwargs) - def add_text(self, x, y, text, color="k"): - self.ax.text(x, y, text, color=color) + def add_text(self, x, y, text, color="k", fontsize=9.5): + self.ax.text(x, y, text, color=color, fontsize=9.5) def range(self, start, stop): cmap = matplotlib.cm.get_cmap('Spectral') - norm = matplotlib.colors.Normalize(vmin=start, vmax=stop-1) + norm = matplotlib.colors.Normalize(vmin=start, vmax=stop) return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 3e834223..0cdd66f4 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -104,17 +104,17 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular, 2 years", - "comparison_curve_label": ["Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "main_curve_upper_label": r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": ["Molecular (2 yrs)"+"\n"+"Conservative", "Atomic"+"\n"+r"(10 $\times$ 3 yrs)"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [0.09, 0.044], - "comparison_label_x_position": [3e16, 1e15], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 2.2e16, 1e15], "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, - "label_x_position": 6e14, #0.02, #1e14, + "label_x_position": 4e14, #0.02, #1e14, "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } From 59a17276b71a2ca8b4f3606b15a50b629a0e3e12 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 15 Jun 2023 01:46:38 -0400 Subject: [PATCH 053/262] Plot formatting --- .../CavitySensitivityCurveProcessor.py | 24 +++++++++---------- tests/Sensitivity_test.py | 14 +++++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index ca4a8bcc..393ced16 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -166,11 +166,6 @@ def InternalRun(self): if self.track_length_axis: self.add_track_length_axis() - # add line for comparison using second config - if self.comparison_curve: - self.add_comparison_curve(label=self.comparison_curve_label) - #self.add_arrow(self.sens_main) - for key, value in self.goals.items(): logger.info('Adding goal: {}'.format(key)) self.add_goal(value*eV, key) @@ -204,9 +199,13 @@ def InternalRun(self): #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color='blue') - self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') + self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) + #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') + # add line for comparison using second config + if self.comparison_curve: + self.add_comparison_curve(label=self.comparison_curve_label) + #self.add_arrow(self.sens_main) # save plot self.save(self.plot_path) @@ -381,10 +380,10 @@ def add_comparison_curve(self, label, color='k'): self.sens_ref.tau_tritium)) logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) """ - colors = ["darkblue", "darkred", "red"] + colors = ["blue", "darkred", "red"] for a in range(len(self.sens_ref)): - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=colors[a]) - self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=colors[a], label=label[a]) + #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) #self.ax.axhline(0.04, color="gray", ls="--") @@ -462,8 +461,8 @@ def add_exposure_sens_line(self, sens, **kwargs): print(limits) self.ax.plot(self.effs, limits, **kwargs) - def add_text(self, x, y, text, color="k", fontsize=9.5): - self.ax.text(x, y, text, color=color, fontsize=9.5) + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 + self.ax.text(x, y, text, color=color) def range(self, start, stop): cmap = matplotlib.cm.get_cmap('Spectral') @@ -471,6 +470,7 @@ def range(self, start, stop): return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): + legend=self.fig.legend(loc='upper left', bbox_to_anchor=(0.15,0,1,0.765), framealpha=0.9) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 0cdd66f4..68385a31 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -91,7 +91,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional @@ -104,17 +104,17 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,1e19], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": ["Molecular (2 yrs)"+"\n"+"Conservative", "Atomic"+"\n"+r"(10 $\times$ 3 yrs)"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "main_curve_upper_label": r"Molecular, conservative", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [2, 0.105, 0.046], - "comparison_label_x_position": [4.5e15, 2.2e16, 1e15], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], "sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, - "label_x_position": 4e14, #0.02, #1e14, + "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } From abf8bf79a46decf22742129f6eec278a8a48c54e Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 15 Jun 2023 09:36:51 -0700 Subject: [PATCH 054/262] starting on exposure plot --- tests/Sensitivity_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 3e834223..5de198db 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -21,7 +21,7 @@ def test_SensitivityCurveProcessor(self): # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_efficiency_curve.pdf", + "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional "track_length_axis": False, "molecular_axis": False, @@ -37,19 +37,19 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, - "sigmaE_theta_r": np.linspace(0.07, 0.15, 10), #in eV, energy broadening from theta and r reconstruction + "sigmaE_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.0002 #2e12 #0.0002 } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { From fdc296cf44b100984910883a052e189d3113fbc0 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 15 Jun 2023 11:43:38 -0700 Subject: [PATCH 055/262] turned efficiency plot into exposure plot also added configurable number of cavities --- mermithid/misc/SensitivityCavityFormulas.py | 2 +- .../CavitySensitivityCurveProcessor.py | 66 +++++++++---------- tests/Sensitivity_test.py | 35 ++++++---- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 978d8ef0..68667b79 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -233,7 +233,7 @@ def CavityRadius(self): def CavityVolume(self): #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) - self.total_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2 + self.total_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 393ced16..9205453d 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -73,9 +73,10 @@ def InternalConfigure(self, params): # main plot configurations self.figsize = reader.read_param(params, 'figsize', (6,6)) + self.legend_location = reader.read_param(params, 'legend_location', 'upper left') self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) - self.efficiency_range = reader.read_param(params, 'efficiency_range', [0.001,0.1]) self.year_range = reader.read_param(params, "years_range", [0.1, 10]) + self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) self.density_axis = reader.read_param(params, "density_axis", True) self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) @@ -151,8 +152,8 @@ def InternalConfigure(self, params): # densities self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 - self.effs = np.logspace(np.log10(self.efficiency_range[0]), np.log10(self.efficiency_range[1]), 10) - self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 10)*year + self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year + self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year return True @@ -293,9 +294,10 @@ def create_plot(self): else: - logger.info("Adding efficiency axis") - ax.set_xlim(self.effs[0], self.effs[-1]) - axis_label = r"Efficiency" + logger.info("Adding exposure axis") + ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) + ax.tick_params(axis='x', which='minor', bottom=True) + axis_label = r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) @@ -345,23 +347,6 @@ def add_track_length_axis(self): logger.warning("No track length axis added since neither atomic nor molecular was requested") self.fig.tight_layout() - """def add_density_comparison_curve(self, label, color='k'): - limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - self.opt_ref = np.argmin(limit) - - rho_opt = self.rhos[self.opt_ref] - logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.Experiment.v_eff/(m**3))) - logger.info('T in Veff: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff)) - logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.Experiment.v_eff* - self.sens_ref.Experiment.LiveTime/ - self.sens_ref.tau_tritium)) - - self.ax.plot(self.rhos*m**3, limit, color=color) - self.ax.axvline(self.rhos[self.opt_ref]*m**3, ls=":", color="gray", alpha=0.4) - - #self.ax.axhline(0.04, color="gray", ls="--") - #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") - self.ax.text(1.5e14, 0.11, label)""" def add_comparison_curve(self, label, color='k'): @@ -448,18 +433,31 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): self.kp_ax[1].plot(self.rhos*m**3, crlb_window, linestyle="--", marker='.', **kwargs) return limits - def add_exposure_sens_line(self, sens, **kwargs): + def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): + + limit = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + sens.Experiment.number_density = rho_opt + + logger.info("Optimum density: {} /m^3".format(rho_opt*m**3)) + logger.info("Years: {}".format(sens.Experiment.livetime/year)) + + standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year + + self.ax.scatter([standard_exposure], [np.min(limit)], marker="s", **kwargs) + limits = [] - for eff in self.effs: - sens.Experiment.efficiency = eff - sens.CavityVolume() - limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit) - rho_opt = self.rhos[opt_ref] - sens.Experiment.number_density = rho_opt + years = [] + for ex in self.exposures: + lt = ex/sens.EffectiveVolume() + years.append(lt/year) + sens.Experiment.livetime = lt limits.append(sens.CL90()/eV) - print(limits) - self.ax.plot(self.effs, limits, **kwargs) + #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) + + unit = r"m$^3$" + self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.2e} / {}".format(rho_opt*m**3, unit), color=kwargs["color"]) def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) @@ -470,7 +468,7 @@ def range(self, start, stop): return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): - legend=self.fig.legend(loc='upper left', bbox_to_anchor=(0.15,0,1,0.765), framealpha=0.9) + legend=self.fig.legend(loc=self.legend_location, bbox_to_anchor=(0.15,0,1,0.765), framealpha=0.9) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 31691ab9..a878bbd0 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -19,33 +19,38 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional + "figsize": (8,6), + "legend_location": "upper right", "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, "density_axis": False, "cavity": True, - "y_limits": [2e-2, 4], - "density_range": [1e12,1e18], - "efficiency_range": [0.0001, 1], + "y_limits": [2e-2, 100], + "density_range": [1e12,1e19], + "exposure_range": [1e-9, 1e4], + #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "comparison_curve_label": r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase III (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, - "sigmaE_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction + "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.0002 #2e12 #0.0002 + "goals_x_position": 1e-8 #2e12 #0.0002 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -108,7 +113,9 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], "sigmae_theta_r": 0.159, @@ -118,9 +125,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From ab4b8b347f64167206cc5c74072dd665650db3e8 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 15 Jun 2023 13:22:44 -0700 Subject: [PATCH 056/262] added a file with Wouter's Hanneke implementation --- mermithid/misc/HannekeFunctions.py | 98 +++++++++++++++++++ mermithid/misc/SensitivityCavityFormulas.py | 30 +++--- mermithid/misc/__init__.py | 1 + .../CavitySensitivityCurveProcessor.py | 2 +- tests/Sensitivity_test.py | 6 +- 5 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 mermithid/misc/HannekeFunctions.py diff --git a/mermithid/misc/HannekeFunctions.py b/mermithid/misc/HannekeFunctions.py new file mode 100644 index 00000000..a2f9f056 --- /dev/null +++ b/mermithid/misc/HannekeFunctions.py @@ -0,0 +1,98 @@ + +import numpy as np +from scipy import special +from numericalunits import T, kB, hbar, e, me, eV, c0, eps0, m, Hz, MHz + +# Physics constants +endpoint = 18563.251*eV # 30472.604*eV # Krypton +bessel_derivative_zero = special.jnp_zeros(0, 1)[0] + +def magneticfield_from_frequency(cyclotron_frequency, kin_energy=endpoint): + # magnetic field for a given cyclotron frequency + return 2*np.pi*me*cyclotron_frequency/e*gamma(kin_energy) + +def gamma(kin_energy): + return kin_energy/(me*c0**2) + 1 + +def beta_factor(kin_energy=endpoint): + # electron speed at kin_energy + return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) + +def larmor_radius(magnetic_field, kin_energy=endpoint, pitch=np.pi/2): + transverse_speed = beta_factor(kin_energy)*c0*np.sin(pitch) + # Larmor radius, exact, can also be apporximated by beta*Xprime01*cavity_radius. + return gamma(kin_energy)*me*transverse_speed/(e*magnetic_field) + +# Hanneke factor for TE011 mode, for an electron at fixed location (r_position, z_position). +def hanneke_factor(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency): + global bessel_derivative_zero + # Calculate the lambda_mnp_squared factor + mode_p = 1 # TE011 mode + z_L = l_cav/2 + # Calculate the lambda_mnp_squared factor + classical_electron_radius_c_squared = e**2 / (4 * np.pi * eps0 * me) + lambda_bessel_part = (1 / (special.jvp(0, bessel_derivative_zero, n=2) * special.jv(0, bessel_derivative_zero))) + constant_factor = - 2 * lambda_bessel_part * classical_electron_radius_c_squared + #constant_factor_unitless = constant_factor/m**3/Hz**2 + #print("Hanneke prefactor [units /m**3/Hz**2]", constant_factor_unitless) + lambda_mnp_squared = constant_factor / (z_L * r_cav**2) # This should be in units of Hz^2 + angular_part = special.jvp(0, bessel_derivative_zero * r_position/r_cav)**2 + axial_part = np.sin(mode_p * np.pi/2 * (z_position/z_L + 1))**2 + + # Efficienctly deal with 2D scans. + if hasattr(axial_part, "__len__") and hasattr(angular_part, "__len__"): + lambda_mnp_squared *= np.outer(angular_part,axial_part) + else: + lambda_mnp_squared *= (angular_part*axial_part) + # Calculate the damping factor + delta = 2*loaded_Q*( cyclotron_frequency/mode_frequency-1) + return lambda_mnp_squared, delta + +# Calculate the radiated power for an electron at fixed location (r_position, z_position) with energy tranverse_kinetic_energy. +def hanneke_radiated_power(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, tranverse_kinetic_energy, mode_frequency=None): + if mode_frequency is None: + # Assume that the center of the mode and the cyclotron frequency are identical + mode_frequency = cyclotron_frequency + + lambda_mnp_squared, delta = hanneke_factor(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency=mode_frequency) + return tranverse_kinetic_energy*(2*loaded_Q/(1+delta**2))*lambda_mnp_squared/(mode_frequency*np.pi*2) + +# Calculate the radiated power for an electron at fixed location (r_position, z_position) with energy tranverse_kinetic_energy. +# Averaging over the larmor power included. +def larmor_orbit_averaged_hanneke_power(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, + kinetic_energy=endpoint, pitch=np.pi/2, mode_frequency=None, n_points=100): + if mode_frequency is None: + # Assume that the center of the mode and the cyclotron frequency are identical + mode_frequency = cyclotron_frequency + tranverse_kinetic_energy = kinetic_energy*np.sin(pitch)**2 + + magnetic_field = magneticfield_from_frequency(cyclotron_frequency, kinetic_energy) + electron_orbit_r = larmor_radius(magnetic_field, kin_energy=kinetic_energy, pitch=pitch) + + random_angles = (np.linspace(0,1,n_points)+np.random.rand())*2*np.pi # equally spaced ponts on a circle with random offset + x_random = electron_orbit_r*np.cos(random_angles) + y_random = electron_orbit_r*np.sin(random_angles) + if hasattr(z_position, "__len__"): + hanneke_powers = np.empty((len(r_position),len(z_position))) + else: + hanneke_powers = np.empty(len(r_position)) + + for i,r_pos_center in enumerate(r_position): + r_pos_orbit = np.sqrt((x_random+r_pos_center)**2 + y_random**2) + # Check for points outside the cavity + if np.any(np.abs(r_pos_orbit) > r_cav): + if hasattr(z_position, "__len__"): + hanneke_powers[i] = np.zeros(len(z_position)) + else: + hanneke_powers[i] = 0 + else: + hanneke_power = hanneke_radiated_power(r_pos_orbit, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, tranverse_kinetic_energy, mode_frequency=mode_frequency) + hanneke_powers[i] = np.mean(hanneke_power.T, axis=-1) + return hanneke_powers + +# Calculate the average radiated power for an electron with radius r_position in a box trap: +def larmor_orbit_averaged_hanneke_power_box(r_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, + kinetic_energy=endpoint, pitch=np.pi/2, mode_frequency=None, n_points=100): + + return larmor_orbit_averaged_hanneke_power(r_position, 0, loaded_Q, l_cav, r_cav, cyclotron_frequency, + kinetic_energy=kinetic_energy, pitch=pitch, mode_frequency=mode_frequency, n_points=n_points)/2 diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 68667b79..687babe7 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -9,6 +9,7 @@ import numpy as np import configparser from numpy import pi +from mermithid.misc.HannekeFunctions import * # Numericalunits is a package to handle units and some natural constants # natural constants @@ -86,17 +87,17 @@ def wavelength(kin_energy, magnetic_field): def kin_energy(freq, magnetic_field): return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) -"""def rad_power(kin_energy, pitch, magnetic_field): +def rad_power(kin_energy, pitch, magnetic_field): # electron radiation power f = frequency(kin_energy, magnetic_field) b = beta(kin_energy) Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) - return Pe""" - -def rad_power(kin_energy, pitch, magnetic_field): - Pe = 2*(e**2*magnetic_field*np.sin(pitch))**2*(gamma(kin_energy)**2-1)/(12*eps0*c0*np.pi*me**2) return Pe +#def rad_power(kin_energy, pitch, magnetic_field): +# Pe = 2*(e**2*magnetic_field*np.sin(pitch))**2*(gamma(kin_energy)**2-1)/(12*eps0*c0*np.pi*me**2) +# return Pe + def track_length(rho, kin_energy=None, molecular=True): if kin_energy is None: kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic @@ -248,14 +249,19 @@ def EffectiveVolume(self): # trapping efficiecny is currently configured. replace with box trap calculation self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.trapping_efficiency + self.effective_volume*=self.Experiment.sri_factor return self.effective_volume def CavityPower(self): # from Hamish's atomic calculator - Jprime_0 = 3.8317 - - self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + #Jprime_0 = 3.8317 + #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), + self.CavityLoadedQ(), + 2*self.Experiment.L_over_D*self.cavity_radius, + self.cavity_radius, + frequency(self.T_endpoint, self.MagneticField.nominal_field))) return self.signal_power @@ -282,7 +288,7 @@ def CavityLoadedQ(self): def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" self.EffectiveVolume() - signal_rate = self.Experiment.number_density*self.effective_volume*self.Experiment.sri_factor*self.last_1ev_fraction/self.tau_tritium + signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) @@ -534,7 +540,7 @@ def syst_frequency_extraction(self): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) # using Pe and alpha (aka slope) from above - Pe = self.CavityPower() #/self.FrequencyExtraction.mode_coupling_efficiency + Pe = self.signal_power #/self.FrequencyExtraction.mode_coupling_efficiency self.larmor_power = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # currently not used self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope @@ -553,12 +559,12 @@ def syst_frequency_extraction(self): # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) if True: #self.time_window_slope_zero >= self.time_window: # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - CRLB_constant = np.sqrt(12) + CRLB_constant = 12 sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.slope_is_zero=True self.best_time_window = self.time_window else: - CRLB_constant = np.sqrt(12) + CRLB_constant = 12 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor diff --git a/mermithid/misc/__init__.py b/mermithid/misc/__init__.py index 44f5ec91..0951b7ba 100644 --- a/mermithid/misc/__init__.py +++ b/mermithid/misc/__init__.py @@ -10,3 +10,4 @@ from . import ConversionFunctions from . import SensitivityFormulas from . import SensitivityCavityFormulas +from . import HannekeFunctions \ No newline at end of file diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 9205453d..1523e7ba 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -457,7 +457,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) unit = r"m$^3$" - self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.2e} / {}".format(rho_opt*m**3, unit), color=kwargs["color"]) + self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} / {}".format(rho_opt*m**3, unit), color=kwargs["color"]) def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index a878bbd0..c873952b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -125,9 +125,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From d44c5068b710e1a14c08ea13e1eb5b212a309fc9 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 15 Jun 2023 13:58:30 -0700 Subject: [PATCH 057/262] some plot changes --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 14 ++++++++++---- tests/Sensitivity_test.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 1523e7ba..8ab0996f 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -74,6 +74,7 @@ def InternalConfigure(self, params): # main plot configurations self.figsize = reader.read_param(params, 'figsize', (6,6)) self.legend_location = reader.read_param(params, 'legend_location', 'upper left') + self.fontsize = reader.read_param(params, 'fontsize', 12) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.year_range = reader.read_param(params, "years_range", [0.1, 10]) self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) @@ -274,6 +275,7 @@ def InternalRun(self): def create_plot(self): # setup axis + plt.rcParams.update({'font.size': self.fontsize}) self.fig, self.ax = plt.subplots(figsize=self.figsize) ax = self.ax ax.set_xscale("log") @@ -405,7 +407,7 @@ def get_relative(val, axis): def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") - self.ax.text(self.goal_x_pos, 0.8*value/eV, label) + self.ax.text(self.goal_x_pos, 0.75*value/eV, label) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -456,8 +458,8 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): limits.append(sens.CL90()/eV) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) - unit = r"m$^3$" - self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} / {}".format(rho_opt*m**3, unit), color=kwargs["color"]) + unit = r"m$^{-3}$" + self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} {}".format(rho_opt*m**3, unit), color=kwargs["color"]) def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) @@ -468,7 +470,11 @@ def range(self, start, stop): return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): - legend=self.fig.legend(loc=self.legend_location, bbox_to_anchor=(0.15,0,1,0.765), framealpha=0.9) + if self.density_axis: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.9, bbox_to_anchor=(0.15,0,1,0.765)) + else: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.9, bbox_to_anchor=(-0.,0,0.95,0.95)) + self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index c873952b..be58eec2 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -50,7 +50,7 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 1e-8 #2e12 #0.0002 + "goals_x_position": 0.5e-8 #2e12 #0.0002 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From 51b282ed0f01841c5dc6dedaf6f9e02f74f4ef68 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 15 Jun 2023 17:20:32 -0700 Subject: [PATCH 058/262] starting to work on Phase II line in exposure plot --- .../CavitySensitivityCurveProcessor.py | 63 +++++++++++++++---- tests/Sensitivity_test.py | 12 ++-- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 8ab0996f..474289a7 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -18,7 +18,7 @@ # Numericalunits is a package to handle units and some natural constants # natural constants from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m +from numericalunits import meV, eV, keV, MeV, cm, m, mm from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W from numericalunits import hour, year, day, ms, ns, s, Hz, kHz, MHz, GHz ppm = 1e-6 @@ -169,8 +169,8 @@ def InternalRun(self): self.add_track_length_axis() for key, value in self.goals.items(): - logger.info('Adding goal: {}'.format(key)) - self.add_goal(value*eV, key) + logger.info('Adding goal: {} = {}'.format(key, value)) + self.add_goal(value, key) # first optimize density limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] @@ -209,8 +209,7 @@ def InternalRun(self): self.add_comparison_curve(label=self.comparison_curve_label) #self.add_arrow(self.sens_main) - # save plot - self.save(self.plot_path) + # PRINT OPTIMUM RESULTS @@ -269,6 +268,11 @@ def InternalRun(self): self.sens_ref[i].print_statistics() self.sens_ref[i].print_systematics() + + # save plot + if self.density_axis == False: + self.add_Phase_II_exposure_sens_line(self.sens_main) + self.save(self.plot_path) return True @@ -292,6 +296,10 @@ def create_plot(self): axis_label = r"(Molecular) number density $\rho\, \, (\mathrm{m}^{-3})$" else: axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" + + ax.set_xlabel(axis_label) + ax.set_ylim(self.ylim) + ax.set_ylabel(r"90% CL $m_\beta$ (eV)") @@ -301,9 +309,14 @@ def create_plot(self): ax.tick_params(axis='x', which='minor', bottom=True) axis_label = r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" - ax.set_xlabel(axis_label) - ax.set_ylim(self.ylim) - ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + ax.set_xlabel(axis_label) + ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + + self.ax2 = ax.twinx() + self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") + self.ax2.set_yscale("log") + self.ax2.set_ylim(self.ylim) if self.make_key_parameter_plots: self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,5)) @@ -406,8 +419,8 @@ def get_relative(val, axis): """ def add_goal(self, value, label): - self.ax.axhline(value/eV, color="gray", ls="--") - self.ax.text(self.goal_x_pos, 0.75*value/eV, label) + self.ax.axhline(value, color="gray", ls="--") + self.ax.text(self.goal_x_pos, 0.75*value, label) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -437,7 +450,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): - limit = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] opt = np.argmin(limit) rho_opt = self.rhos[opt] sens.Experiment.number_density = rho_opt @@ -455,12 +468,38 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): lt = ex/sens.EffectiveVolume() years.append(lt/year) sens.Experiment.livetime = lt - limits.append(sens.CL90()/eV) + limits.append(sens.sensitivity()/eV**2) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) unit = r"m$^{-3}$" self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} {}".format(rho_opt*m**3, unit), color=kwargs["color"]) + def add_Phase_II_exposure_sens_line(self, sens): + sens.Experiment.number_density = 13.4e16/m**3 + sens.effective_volume = 1.2*mm**3 + sens.Experiment.sri_factor = 7.45e16/13.4e16 + sens.Experiment.livetime = 7185228*s + + + standard_exposure = sens.effective_volume*sens.Experiment.livetime/m**3/year + print(standard_exposure) + + phaseIIsens = 9822 + + self.ax.scatter([standard_exposure], [phaseIIsens], marker="s", color='k', label="Phase II") + + limits = [] + years = [] + for ex in self.exposures: + lt = ex/sens.effective_volume + years.append(lt/year) + sens.Experiment.livetime = lt + limits.append(sens.sensitivity()/eV**2) + #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) + + unit = r"m$^{-3}$" + self.ax.plot(self.exposures/m**3/year, limits, label="T2 Density = {:.1e} {}".format(7.5e16*m**3, unit), color='k') + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index be58eec2..21d297e9 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -30,14 +30,14 @@ def test_SensitivityCurveProcessor(self): "atomic_axis": False, "density_axis": False, "cavity": True, - "y_limits": [2e-2, 100], + "y_limits": [2e-2, 200], "density_range": [1e12,1e19], - "exposure_range": [1e-9, 1e4], + "exposure_range": [1e-11, 1e4], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", @@ -125,9 +125,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From ab4be457fbde1477ce04f78c7dd1774ba389b36f Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Fri, 16 Jun 2023 11:41:02 -0400 Subject: [PATCH 059/262] Format plots; fix CRLB formula --- mermithid/misc/SensitivityCavityFormulas.py | 4 ++-- .../Sensitivity/CavitySensitivityCurveProcessor.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 978d8ef0..32482973 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -553,12 +553,12 @@ def syst_frequency_extraction(self): # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) if True: #self.time_window_slope_zero >= self.time_window: # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - CRLB_constant = np.sqrt(12) + CRLB_constant = 12 sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.slope_is_zero=True self.best_time_window = self.time_window else: - CRLB_constant = np.sqrt(12) + CRLB_constant = 12 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 393ced16..d65fde58 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -72,7 +72,7 @@ def InternalConfigure(self, params): # main plot configurations - self.figsize = reader.read_param(params, 'figsize', (6,6)) + self.figsize = reader.read_param(params, 'figsize', (6,5)) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.efficiency_range = reader.read_param(params, 'efficiency_range', [0.001,0.1]) self.year_range = reader.read_param(params, "years_range", [0.1, 10]) @@ -420,7 +420,7 @@ def get_relative(val, axis): def add_goal(self, value, label): self.ax.axhline(value/eV, color="gray", ls="--") - self.ax.text(self.goal_x_pos, 0.8*value/eV, label) + self.ax.text(self.goal_x_pos, 0.75*value/eV, label) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -470,7 +470,7 @@ def range(self, start, stop): return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): - legend=self.fig.legend(loc='upper left', bbox_to_anchor=(0.15,0,1,0.765), framealpha=0.9) + legend=self.fig.legend(loc='upper left', bbox_to_anchor=(0.15,0,1,0.758), framealpha=0.95) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", From 6a3c3634bd991c46d6d8cffa8acbdf7a37dd129c Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Fri, 16 Jun 2023 13:12:00 -0400 Subject: [PATCH 060/262] Update plot formatting; case with complex signal for CRLB (still being resolved) --- mermithid/misc/SensitivityCavityFormulas.py | 6 +++--- .../Sensitivity/CavitySensitivityCurveProcessor.py | 4 ++-- tests/Sensitivity_test.py | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 687babe7..b023b8c2 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -559,15 +559,15 @@ def syst_frequency_extraction(self): # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) if True: #self.time_window_slope_zero >= self.time_window: # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - CRLB_constant = 12 + CRLB_constant = 6 sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.slope_is_zero=True self.best_time_window = self.time_window else: - CRLB_constant = 12 + CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 180*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 474289a7..2fc34a8e 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -510,9 +510,9 @@ def range(self, start, stop): def save(self, savepath, **kwargs): if self.density_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.9, bbox_to_anchor=(0.15,0,1,0.765)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.9, bbox_to_anchor=(-0.,0,0.95,0.95)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.95,0.95)) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 21d297e9..02bbfdd4 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -100,13 +100,14 @@ def test_SensitivityCurveProcessor(self): #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional + "figsize": (6.7,6), "track_length_axis": True, "molecular_axis": True, "atomic_axis": True, "density_axis": True, "cavity": True, "y_limits": [2e-2, 4], - "density_range": [1e12,1e19], + "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular, conservative", #r"Molecular"+"\n"+"Reaching target", @@ -122,12 +123,12 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 2e12, #0.0002 + "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 5208de578c04c8005f4408631ce46f4db592034f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 16 Jun 2023 11:45:40 -0700 Subject: [PATCH 061/262] phase II curve matches now --- mermithid/misc/SensitivityCavityFormulas.py | 11 ++++---- .../CavitySensitivityCurveProcessor.py | 28 ++++++++++++++----- tests/Sensitivity_test.py | 8 ++++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 687babe7..e250b30b 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -221,6 +221,7 @@ def __init__(self, config_path): self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + self.CRLB_constant = 12 self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -287,7 +288,7 @@ def CavityLoadedQ(self): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - self.EffectiveVolume() + #self.EffectiveVolume() signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): @@ -295,6 +296,8 @@ def SignalRate(self): signal_rate *= avg_n_T_atoms else: signal_rate *= 2 + if hasattr(self.Experiment, 'active_gas_fraction'): + signal_rate *= self.Experiment.active_gas_fraction return signal_rate def BackgroundRate(self): @@ -373,6 +376,7 @@ def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas H2_iso_avg_num += val elif key=='H2' or key=='HD' or key=='D2': pass + logger.info("Activive fraction: {}".format(gas_fractions['H2']*H2_iso_avg_num)) return gas_fractions['H2']*H2_iso_avg_num def BToKeErr(self, BErr, B): @@ -559,9 +563,7 @@ def syst_frequency_extraction(self): # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) if True: #self.time_window_slope_zero >= self.time_window: # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - CRLB_constant = 12 - sigma_f_CRLB = np.sqrt((CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - self.slope_is_zero=True + sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.best_time_window = self.time_window else: CRLB_constant = 12 @@ -572,7 +574,6 @@ def syst_frequency_extraction(self): sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) - self.slope_is_zero = False self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])] """# uncertainty in alpha diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 474289a7..8e0521e0 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -56,6 +56,7 @@ def InternalConfigure(self, params): self.config_file_path = reader.read_param(params, 'config_file_path', "required") self.comparison_config_file_path = reader.read_param(params, 'comparison_config_file_path', '') self.plot_path = reader.read_param(params, 'plot_path', "required") + self.PhaseII_path = reader.read_param(params, 'PhaseII_config_path', '') # labels @@ -91,6 +92,7 @@ def InternalConfigure(self, params): self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) # other plot self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) @@ -105,6 +107,9 @@ def InternalConfigure(self, params): # goals self.goals = reader.read_param(params, "goals", {}) + + if self.add_PhaseII: + self.sens_PhaseII = CavitySensitivity(self.PhaseII_path) # setup sensitivities @@ -270,8 +275,8 @@ def InternalRun(self): self.sens_ref[i].print_systematics() # save plot - if self.density_axis == False: - self.add_Phase_II_exposure_sens_line(self.sens_main) + if self.density_axis == False and self.add_PhaseII: + self.add_Phase_II_exposure_sens_line(self.sens_PhaseII) self.save(self.plot_path) return True @@ -475,18 +480,25 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} {}".format(rho_opt*m**3, unit), color=kwargs["color"]) def add_Phase_II_exposure_sens_line(self, sens): - sens.Experiment.number_density = 13.4e16/m**3 + sens.Experiment.number_density = 2.09e17/m**3 sens.effective_volume = 1.2*mm**3 - sens.Experiment.sri_factor = 7.45e16/13.4e16 + sens.Experiment.sri_factor = 1 #0.389*0.918*0.32 sens.Experiment.livetime = 7185228*s + sens.CRLB_constant = 180 standard_exposure = sens.effective_volume*sens.Experiment.livetime/m**3/year - print(standard_exposure) + sens.print_systematics() + sens.print_statistics() + sens.sensitivity() + logger.info("Phase II sensitivity for exposure {} calculated: {}".format(standard_exposure, sens.sensitivity()/eV**2)) + phaseIIsens = 9822 + phaseIIsense_error = 1520 + exposure_error = np.sqrt((standard_exposure*0.008)**2 + (standard_exposure*0.09)**2) - self.ax.scatter([standard_exposure], [phaseIIsens], marker="s", color='k', label="Phase II") + self.ax.errorbar([standard_exposure], [phaseIIsens], xerr=[exposure_error], yerr=[phaseIIsense_error], fmt='o', color='k', label="Phase II") limits = [] years = [] @@ -498,7 +510,9 @@ def add_Phase_II_exposure_sens_line(self, sens): #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) unit = r"m$^{-3}$" - self.ax.plot(self.exposures/m**3/year, limits, label="T2 Density = {:.1e} {}".format(7.5e16*m**3, unit), color='k') + self.ax.plot(self.exposures/m**3/year, limits, label="T2 Density = {:.1e} {}".format(7.5e16, unit), color='k') + print(years) + print(limits) def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 21d297e9..044434c2 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -30,9 +30,11 @@ def test_SensitivityCurveProcessor(self): "atomic_axis": False, "density_axis": False, "cavity": True, - "y_limits": [2e-2, 200], + "add_PhaseII": True, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "y_limits": [20e-3, 400], "density_range": [1e12,1e19], - "exposure_range": [1e-11, 1e4], + "exposure_range": [1e-11, 1e3], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular, conservative", @@ -40,7 +42,7 @@ def test_SensitivityCurveProcessor(self): "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", From c95b0af2c6abfd35d4e20f92fda261ba24d55e6c Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 16 Jun 2023 15:44:31 -0700 Subject: [PATCH 062/262] working on exposure plot --- .../CavitySensitivityCurveProcessor.py | 33 ++++++++++++------- tests/Sensitivity_test.py | 14 ++++---- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index d490adbc..6149012e 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -63,7 +63,7 @@ def InternalConfigure(self, params): self.main_curve_upper_label = reader.read_param(params, 'main_curve_upper_label', r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") self.comparison_curve_label = reader.read_param(params, 'comparison_curve_label', r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") - + self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) # options self.comparison_curve = reader.read_param(params, 'comparison_curve', False) @@ -93,6 +93,7 @@ def InternalConfigure(self, params): self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) + self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) # other plot self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) @@ -168,6 +169,10 @@ def InternalConfigure(self, params): def InternalRun(self): self.create_plot() + + # optionally add Phase II curve and point to exposure plot + if self.density_axis == False and self.add_PhaseII: + self.add_Phase_II_exposure_sens_line(self.sens_PhaseII) # add second and third x axis for track lengths if self.track_length_axis: @@ -275,8 +280,6 @@ def InternalRun(self): self.sens_ref[i].print_systematics() # save plot - if self.density_axis == False and self.add_PhaseII: - self.add_Phase_II_exposure_sens_line(self.sens_PhaseII) self.save(self.plot_path) return True @@ -385,9 +388,9 @@ def add_comparison_curve(self, label, color='k'): self.sens_ref.tau_tritium)) logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) """ - colors = ["blue", "darkred", "red"] + for a in range(len(self.sens_ref)): - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=colors[a], label=label[a]) + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) @@ -425,7 +428,7 @@ def get_relative(val, axis): def add_goal(self, value, label): self.ax.axhline(value, color="gray", ls="--") - self.ax.text(self.goal_x_pos, 0.75*value, label) + self.ax.text(self.goal_x_pos, self.goals_y_rel_position*value, label) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -465,6 +468,8 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year + print(kwargs) + self.ax.scatter([standard_exposure], [np.min(limit)], marker="s", **kwargs) limits = [] @@ -476,8 +481,12 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): limits.append(sens.sensitivity()/eV**2) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) + if sens.Experiment.atomic: + gas = "T" + else: + gas = r"T$_2$" unit = r"m$^{-3}$" - self.ax.plot(self.exposures/m**3/year, limits, label="Density = {:.1e} {}".format(rho_opt*m**3, unit), color=kwargs["color"]) + self.ax.plot(self.exposures/m**3/year, limits, label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit), color=kwargs["color"]) def add_Phase_II_exposure_sens_line(self, sens): sens.Experiment.number_density = 2.09e17/m**3 @@ -498,7 +507,7 @@ def add_Phase_II_exposure_sens_line(self, sens): phaseIIsense_error = 1520 exposure_error = np.sqrt((standard_exposure*0.008)**2 + (standard_exposure*0.09)**2) - self.ax.errorbar([standard_exposure], [phaseIIsens], xerr=[exposure_error], yerr=[phaseIIsense_error], fmt='o', color='k', label="Phase II") + self.ax.errorbar([standard_exposure], [phaseIIsens], xerr=[exposure_error], yerr=[phaseIIsense_error], fmt='.', color='k', label="Phase II (measured)") limits = [] years = [] @@ -510,9 +519,9 @@ def add_Phase_II_exposure_sens_line(self, sens): #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) unit = r"m$^{-3}$" - self.ax.plot(self.exposures/m**3/year, limits, label="T2 Density = {:.1e} {}".format(7.5e16, unit), color='k') - print(years) - print(limits) + gas = r"T$_2$" + self.ax.plot(self.exposures/m**3/year, limits, label="{} density = {:.1e} {}".format(gas, 7.5e16, unit), color='k', linestyle=':') + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) @@ -526,7 +535,7 @@ def save(self, savepath, **kwargs): if self.density_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.95,0.95)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.87,0.97)) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index ceb6d585..1a28c080 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -32,19 +32,20 @@ def test_SensitivityCurveProcessor(self): "cavity": True, "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "y_limits": [20e-3, 400], + "y_limits": [10e-3, 500], "density_range": [1e12,1e19], - "exposure_range": [1e-11, 1e3], + "exposure_range": [1e-11, 1e4], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular, conservative", + "main_curve_upper_label": r"T$_2$ in TE011 cavity, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], + "comparison_curve_label": ["T in TE011 cavity, reaching target"], + "comparison_curve_colors": ["red"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, @@ -52,7 +53,8 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.5e-8 #2e12 #0.0002 + "goals_x_position": 0.5e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From 3723854678a2a46048932d0bb6ea28ca9ea24266 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 20 Jun 2023 09:25:56 -0700 Subject: [PATCH 063/262] more exposure plot tweaks --- mermithid/misc/SensitivityCavityFormulas.py | 1 + .../CavitySensitivityCurveProcessor.py | 35 ++++++++++++++++--- tests/Sensitivity_test.py | 13 +++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 1505d0c0..b882f1c4 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -240,6 +240,7 @@ def CavityVolume(self): logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Radius: {} cm".format(round(self.cavity_radius/cm, 3))) + logger.info("Total volume {} m^3".format(round(self.total_volume/m**3))) return self.total_volume diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 6149012e..98a0296a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -486,7 +486,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): else: gas = r"T$_2$" unit = r"m$^{-3}$" - self.ax.plot(self.exposures/m**3/year, limits, label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit), color=kwargs["color"]) + self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) def add_Phase_II_exposure_sens_line(self, sens): sens.Experiment.number_density = 2.09e17/m**3 @@ -495,7 +495,6 @@ def add_Phase_II_exposure_sens_line(self, sens): sens.Experiment.livetime = 7185228*s sens.CRLB_constant = 180 - standard_exposure = sens.effective_volume*sens.Experiment.livetime/m**3/year sens.print_systematics() sens.print_statistics() @@ -509,6 +508,7 @@ def add_Phase_II_exposure_sens_line(self, sens): self.ax.errorbar([standard_exposure], [phaseIIsens], xerr=[exposure_error], yerr=[phaseIIsense_error], fmt='.', color='k', label="Phase II (measured)") + limits = [] years = [] for ex in self.exposures: @@ -520,7 +520,34 @@ def add_Phase_II_exposure_sens_line(self, sens): unit = r"m$^{-3}$" gas = r"T$_2$" - self.ax.plot(self.exposures/m**3/year, limits, label="{} density = {:.1e} {}".format(gas, 7.5e16, unit), color='k', linestyle=':') + self.ax.plot(self.exposures/m**3/year, limits, color='k', linestyle=':')#, label="{} density = {:.1e} {}".format(gas, 7.5e16, unit)) + + def get_relative(val, axis): + xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() + return (np.log10(val)-np.log10(xmin))/(np.log10(xmax)-np.log10(xmin)) + + + """x_start = get_relative(1e-3, "x") + y_start = get_relative(6e1, "y") + x_stop = get_relative(1e-4, "x") + y_stop = get_relative(3e1, "y")""" + + x_start = get_relative(4e-7, "x") + y_start = get_relative(6e3, "y") + x_stop = get_relative(5e-8, "x") + y_stop = get_relative(8e2, "y") + self.ax.arrow(x_start, y_start, x_stop-x_start, y_stop-y_start, + transform = self.ax.transAxes, + facecolor = 'black', + edgecolor='k', + length_includes_head=True, + head_width=0.01, + head_length=0.01, + ) + print(x_start, y_start) + self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*1.01, y_start*1.01],textcoords="axes fraction", fontsize=13) + + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 @@ -535,7 +562,7 @@ def save(self, savepath, **kwargs): if self.density_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.87,0.97)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.9,0.97)) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 1a28c080..ebb41d97 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -23,7 +23,8 @@ def test_SensitivityCurveProcessor(self): #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional - "figsize": (8,6), + "figsize": (10,6), + "fontsize": 14, "legend_location": "upper right", "track_length_axis": False, "molecular_axis": False, @@ -37,15 +38,15 @@ def test_SensitivityCurveProcessor(self): "exposure_range": [1e-11, 1e4], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"T$_2$ in TE011 cavity, conservative", + "main_curve_upper_label": r"CRES with T$_2$, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, - "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": ["T in TE011 cavity, reaching target"], - "comparison_curve_colors": ["red"], + "comparison_curve_label": [r"CRES with T$_2$, reaching target", "CRES with T, conservative", "CRES with T, reaching target"], + "comparison_curve_colors": ["blue", "darkred", "red"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, From a922af9d3d5205475b196be11c83cf16e57afe52 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 20 Jun 2023 18:19:29 -0400 Subject: [PATCH 064/262] Fix T2 E0 value; use consistent CRLB constants --- mermithid/misc/Constants.py | 2 +- mermithid/misc/SensitivityCavityFormulas.py | 2 +- tests/Sensitivity_test.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mermithid/misc/Constants.py b/mermithid/misc/Constants.py index a967fae4..bf60d290 100644 --- a/mermithid/misc/Constants.py +++ b/mermithid/misc/Constants.py @@ -33,7 +33,7 @@ def Vud(): return 0.97425 #CKM element #Beta decay-specific physical constants def QT(): return 18563.251 #For atomic tritium (eV), from Bodine et al. (2015) -def QT2(): return 18573.24 #For molecular tritium (eV), Bodine et al. (2015) +def QT2(): return 18574.01 #For molecular tritium (eV), Bodine et al. (2015) and Meyers et al. Discussion: https://projecteight.slack.com/archives/CG5TY2UE7/p1649963449399179 def Rn(): return 2.8840*10**(-3) #Helium-3 nuclear radius in units of me, from Kleesiek et al. (2018): https://arxiv.org/pdf/1806.00369.pdf def M_3He_in_me(): return 5497.885 #Helium-3 mass in units of me, Kleesiek et al. (2018) def atomic_num(): return 2. #For helium-3 diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 1505d0c0..91ccd3d4 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -569,7 +569,7 @@ def syst_frequency_extraction(self): CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 15*CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index ceb6d585..9a11d629 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -54,9 +54,9 @@ def test_SensitivityCurveProcessor(self): "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.5e-8 #2e12 #0.0002 } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { From 222e7dfa47a86eed76d25e7163c09004a69b2030 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 20 Jun 2023 15:22:28 -0700 Subject: [PATCH 065/262] added sideband correction uncertainty calculation --- mermithid/misc/SensitivityCavityFormulas.py | 59 ++++++++++++------- .../CavitySensitivityCurveProcessor.py | 15 ++++- tests/Sensitivity_test.py | 8 +-- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index b882f1c4..32dd4231 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -123,8 +123,10 @@ def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, m # An electron will see a slightly different magnetic field # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. - y = (90-max_pitch_angle)/4 - return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) + #y = (90-max_pitch_angle)/4 + phi_rad = (90-max_pitch_angle)/180*pi + return 0.16*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) + #return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) # Noise power entering the amplifier, inclding the transmitted noise from the cavity and the reflected noise from the circulator. # Insertion loss is included. @@ -472,7 +474,7 @@ def syst_doppler_broadening(self): delta_trans = sigma_trans*self.DopplerBroadening.fraction_uncertainty_on_doppler_broadening return sigma_trans, delta_trans - def calculate_tau_snr(self, time_window): + def calculate_tau_snr(self, time_window, sideband_power_fraction=1): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) @@ -501,10 +503,7 @@ def calculate_tau_snr(self, time_window): # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # logger.info("Power: {}".format(Pe/W)) - Pe = self.signal_power - - if self.FrequencyExtraction.crlb_on_sidebands: - Pe*=self.FrequencyExtraction.sideband_power_fraction + Pe = self.signal_power * sideband_power_fraction P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) tau_snr = kB*tn_system_fft/P_signal_received @@ -555,27 +554,27 @@ def syst_frequency_extraction(self): tau_snr_full_length = self.calculate_tau_snr(self.time_window) tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero) - # use different crlb based on slope # delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+self.slope*ms, self.MagneticField.nominal_field)) # logger.info("slope is {} Hz/ms".format(self.slope/Hz*ms)) # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) - if True: #self.time_window_slope_zero >= self.time_window: - # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - self.best_time_window = self.time_window - else: - CRLB_constant = 6 - sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + # if True: #self.time_window_slope_zero >= self.time_window: + # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) + sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + self.best_time_window = self.time_window + + """ non constant slope + CRLB_constant = 6 + sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) - - # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) - self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])] + sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + + sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) + + # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) + self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])]""" """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) @@ -585,6 +584,24 @@ def syst_frequency_extraction(self): # sigma_f from Cramer-Rao lower bound in eV self.sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*delta_sigma_f_CRLB*c0**2 + + # sigma_f from pitch angle reconstruction + if self.FrequencyExtraction.crlb_on_sidebands: + tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) + sigma_f_sideband_crlb = np.sqrt((self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + + # calculate uncertainty of energy correction for pitch angle + var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 + max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + self.T_endpoint, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + # 0.16 is the trap quadratic term. 3.8317 is the first 0 in J'0 + var_f0_reconstruction *= (8 * 0.16 * (3.8317*self.Experiment.L_over_D / (np.pi * beta(self.T_endpoint)))**2*max_ax_freq/endpoint_frequency)**2*(1/3.0) + sigma_f0_reconstruction = np.sqrt(var_f0_reconstruction) + self.sigma_K_reconstruction = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f0_reconstruction*c0**2 + + self.sigma_K_f_CRLB = np.sqrt(self.sigma_K_f_CRLB**2 + self.sigma_K_reconstruction**2) + # combined sigma_f in eV sigma_f = np.sqrt(self.sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 98a0296a..d06f3bac 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -239,9 +239,15 @@ def InternalRun(self): self.sens_main.MagneticField.sigmae_theta = 0 * eV logger.info('Main curve (molecular):') + # set optimum density back + self.sens_main.CL90(Experiment={"number_density": rho_opt}) logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) + + if self.sens_main.FrequencyExtraction.crlb_on_sidebands: + logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -260,12 +266,17 @@ def InternalRun(self): limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] opt_ref = np.argmin(limit_ref) rho_opt_ref = self.rhos[opt_ref] - self.sens_ref[i].Experiment.number_density = rho_opt_ref + #self.sens_ref[i].Experiment.number_density = rho_opt_ref + self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) logger.info('Comparison curve:') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) + + if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: + logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) + logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* @@ -562,7 +573,7 @@ def save(self, savepath, **kwargs): if self.density_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.9,0.97)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.89,0.97)) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index ebb41d97..79f2793b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -24,7 +24,7 @@ def test_SensitivityCurveProcessor(self): "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional "figsize": (10,6), - "fontsize": 14, + "fontsize": 15, "legend_location": "upper right", "track_length_axis": False, "molecular_axis": False, @@ -38,14 +38,14 @@ def test_SensitivityCurveProcessor(self): "exposure_range": [1e-11, 1e4], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"CRES with T$_2$, conservative", + "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": [r"CRES with T$_2$, reaching target", "CRES with T, conservative", "CRES with T, reaching target"], + "comparison_curve_label": [r"Molecular, reaching target", "Atomic, conservative", "Atomic, reaching target"], "comparison_curve_colors": ["blue", "darkred", "red"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, @@ -54,7 +54,7 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.5e-10, #2e12 #0.0002 + "goals_x_position": 0.3e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") From 5c235fcb2f8e9b5a39375c5f876db36c831baba7 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 20 Jun 2023 16:14:39 -0700 Subject: [PATCH 066/262] adding exposure point for last ref experiment --- .../CavitySensitivityCurveProcessor.py | 51 ++++++++++++++++--- tests/Sensitivity_test.py | 7 +-- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index d06f3bac..da8ff155 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -94,6 +94,7 @@ def InternalConfigure(self, params): self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) + self.add_1year_1cav_point_to_last_ref = reader.read_param(params, "add_1year_1cav_point_to_last_ref", False) # other plot self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) @@ -404,7 +405,9 @@ def add_comparison_curve(self, label, color='k'): limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) - + if not self.density_axis and self.add_1year_1cav_point_to_last_ref: + print("Going to add single exposure point") + self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) #self.ax.axhline(0.04, color="gray", ls="--") #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") @@ -466,6 +469,39 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): self.kp_ax[1].plot(self.rhos*m**3, crlb_slope_zero_window, color='green', marker='.') self.kp_ax[1].plot(self.rhos*m**3, crlb_window, linestyle="--", marker='.', **kwargs) return limits + + def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color="red", label="Single cavity running 1 year"): + print("Adding exposure point") + + + + #exposure_fraction = n_cavities*livetime/(sens.Experiment.n_cavities*sens.Experiment.livetime) + + sens.Experiment.n_cavities = n_cavities + sens.Experiment.livetime = livetime + sens.CavityVolume() + sens.EffectiveVolume() + sens.CavityPower() + + limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + sens.Experiment.number_density = rho_opt + + + limit = sens.sensitivity()/eV**2 + + standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year #*exposure_fraction + #lt = standard_exposure/sens.EffectiveVolume() + #sens.Experiment.livetime = lt + limit = sens.sensitivity()/eV**2 + + + self.ax.scatter([standard_exposure], [limit], marker="o", color=color, label=label, zorder=10) + + print(standard_exposure, limit) + + def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): @@ -478,8 +514,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): logger.info("Years: {}".format(sens.Experiment.livetime/year)) standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - - print(kwargs) + self.ax.scatter([standard_exposure], [np.min(limit)], marker="s", **kwargs) @@ -543,10 +578,10 @@ def get_relative(val, axis): x_stop = get_relative(1e-4, "x") y_stop = get_relative(3e1, "y")""" - x_start = get_relative(4e-7, "x") - y_start = get_relative(6e3, "y") - x_stop = get_relative(5e-8, "x") - y_stop = get_relative(8e2, "y") + x_start = get_relative(3e-7, "x") + y_start = get_relative(7e3, "y") + x_stop = get_relative(4e-8, "x") + y_stop = get_relative(9.0e2, "y") self.ax.arrow(x_start, y_start, x_stop-x_start, y_stop-y_start, transform = self.ax.transAxes, facecolor = 'black', @@ -556,7 +591,7 @@ def get_relative(val, axis): head_length=0.01, ) print(x_start, y_start) - self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*1.01, y_start*1.01],textcoords="axes fraction", fontsize=13) + self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*0.9, y_start*1.01],textcoords="axes fraction", fontsize=13) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index daed219a..341577d8 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -32,6 +32,7 @@ def test_SensitivityCurveProcessor(self): "density_axis": False, "cavity": True, "add_PhaseII": True, + "add_1year_1cav_point_to_last_ref": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "y_limits": [10e-3, 500], "density_range": [1e12,1e19], @@ -57,9 +58,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 0.3e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { From c0d7b0a60eef9f8622b0609a094af60f7bfb965e Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 20 Jun 2023 19:34:40 -0400 Subject: [PATCH 067/262] Smaller marker size --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 4 ++-- tests/Sensitivity_test.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index d06f3bac..724ecea4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -157,7 +157,7 @@ def InternalConfigure(self, params): else: raise ValueError("No experiment is configured to be atomic") - # densities + # densities, exposures, runtimes self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year @@ -481,7 +481,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): print(kwargs) - self.ax.scatter([standard_exposure], [np.min(limit)], marker="s", **kwargs) + self.ax.scatter([standard_exposure], [np.min(limit)], s=15, marker="s", **kwargs) limits = [] years = [] diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index daed219a..79f2793b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -57,9 +57,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 0.3e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { From 155532038ed739a337c4f384c03a609bcdc3f55a Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 20 Jun 2023 22:37:57 -0400 Subject: [PATCH 068/262] Print info for single-cavity atomic optimization --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index e71e5f2a..2fd9db6b 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -406,7 +406,7 @@ def add_comparison_curve(self, label, color='k'): #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) if not self.density_axis and self.add_1year_1cav_point_to_last_ref: - print("Going to add single exposure point") + logger.info("Going to add single exposure point") self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) #self.ax.axhline(0.04, color="gray", ls="--") #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") @@ -471,7 +471,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): return limits def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color="red", label="Single cavity running 1 year"): - print("Adding exposure point") + logger.info("Adding exposure point") @@ -497,9 +497,11 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" limit = sens.sensitivity()/eV**2 - self.ax.scatter([standard_exposure], [limit], marker="o", color=color, label=label, zorder=10) + self.ax.scatter([standard_exposure], [limit], marker="o", s=15, color=color, label=label, zorder=10) - print(standard_exposure, limit) + logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.28*limit))) + sens.print_statistics() + sens.print_systematics() @@ -516,7 +518,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - self.ax.scatter([standard_exposure], [np.min(limit)], s=15, marker="s", **kwargs) + self.ax.scatter([standard_exposure], [np.min(limit)], s=17, marker="s", **kwargs) limits = [] years = [] From 97e7c3b085b2706d06a301e4230d525fa9018180 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 21 Jun 2023 11:06:03 -0400 Subject: [PATCH 069/262] Plot best-case T2; allow for plotting one curve only --- .../CavitySensitivityCurveProcessor.py | 41 ++++++++++--------- tests/Sensitivity_test.py | 38 +++++++++++++++-- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 2fd9db6b..0b293b9e 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -136,6 +136,8 @@ def InternalConfigure(self, params): for i in range(len(self.sens_ref)): is_atomic.append(self.sens_ref[i].Experiment.atomic) self.sens_ref_is_atomic = is_atomic + else: + self.sens_ref_is_atomic = [False] # check atomic and molecular if self.molecular_axis: @@ -143,7 +145,7 @@ def InternalConfigure(self, params): #self.molecular_sens = self.sens_main logger.info("Main curve is molecular") elif False not in self.sens_ref_is_atomic: - raise ValueError("No experiment is configured to be molecular") + logger.warn("No experiment is configured to be molecular") #self.molecular_sens = self.sens_ref #logger.info("Comparison curve is molecular") #else: @@ -156,7 +158,7 @@ def InternalConfigure(self, params): #self.atomic_sens = self.sens_ref logger.info("A comparison curve is atomic") else: - raise ValueError("No experiment is configured to be atomic") + logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 @@ -263,27 +265,28 @@ def InternalRun(self): self.sens_main.print_systematics() # Optimize atomic density - for i in range(len(self.sens_ref)): - limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit_ref) - rho_opt_ref = self.rhos[opt_ref] - #self.sens_ref[i].Experiment.number_density = rho_opt_ref - self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) - - logger.info('Comparison curve:') - logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) - logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) - logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) + if self.comparison_curve: + for i in range(len(self.sens_ref)): + limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit_ref) + rho_opt_ref = self.rhos[opt_ref] + #self.sens_ref[i].Experiment.number_density = rho_opt_ref + self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + + logger.info('Comparison curve:') + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) + logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) - if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: - logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) + if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: + logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) - logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) - logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* + logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) + logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) - logger.info('Signal in last eV: {}'.format(self.sens_ref[i].last_1ev_fraction*eV**3* + logger.info('Signal in last eV: {}'.format(self.sens_ref[i].last_1ev_fraction*eV**3* rho_opt_ref*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 341577d8..0085dac3 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -58,9 +58,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 0.3e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -106,7 +106,7 @@ def test_SensitivityCurveProcessor(self): #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional - "figsize": (6.7,6), + "figsize": (7.0,6), "track_length_axis": True, "molecular_axis": True, "atomic_axis": True, @@ -136,6 +136,36 @@ def test_SensitivityCurveProcessor(self): sens_curve.Configure(sens_config_dict) sens_curve.Run() + + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_best_case_curve.pdf", + # optional + "figsize": (6.7,6), + "track_length_axis": False, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r"Molecular, best-case-scenario", + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e12, + "plot_key_parameters": True + } + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() + def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From c13c80a0810c20da20397fe043b0327ff1eeaa9c Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 21 Jun 2023 10:24:03 -0700 Subject: [PATCH 070/262] changed exposure point label --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 12 ++++++++++-- tests/Sensitivity_test.py | 8 ++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 0b293b9e..b025f905 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -473,10 +473,18 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): self.kp_ax[1].plot(self.rhos*m**3, crlb_window, linestyle="--", marker='.', **kwargs) return limits - def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color="red", label="Single cavity running 1 year"): + def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color="red"): logger.info("Adding exposure point") - + if livetime/year % 1 == 0: + livetime_for_label = int(round(livetime/year)) + else: + livetime_for_label = round(livetime/year, 1) + + if sens.Experiment.atomic: + label="Atomic, {} cavity, {} year".format(n_cavities, livetime_for_label) + else: + label="Molecular, {} cavity, {} year".format(n_cavities, livetime_for_label) #exposure_fraction = n_cavities*livetime/(sens.Experiment.n_cavities*sens.Experiment.livetime) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 0085dac3..af8868de 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -55,12 +55,12 @@ def test_SensitivityCurveProcessor(self): "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.3e-10, #2e12 #0.0002 + "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { From 2bf4f03e0f02350c76f4e22228558b17a5166c54 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 21 Jun 2023 10:57:06 -0700 Subject: [PATCH 071/262] removing accidentally pushed sensitivity plot --- ...tivity_vs_density_curve_with_sigmaBcorrs.pdf | Bin 30695 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf diff --git a/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf b/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf deleted file mode 100644 index 7866abec64c7040c644566bbb8fcc89efcb8a8f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30695 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 Date: Wed, 21 Jun 2023 14:28:17 -0400 Subject: [PATCH 072/262] Make uncertainty on MFS configurable --- mermithid/misc/SensitivityCavityFormulas.py | 7 ++++--- ...tivity_vs_density_curve_with_sigmaBcorrs.pdf | Bin 30695 -> 0 bytes 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 32dd4231..df826778 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -33,7 +33,7 @@ last_1ev_fraction_molecular = 1.67364e-13/eV**3 ground_state_width = 0.436 * eV -ground_state_width_uncertainty = 0.001*0.436*eV +#ground_state_width_uncertainty = 0.001*0.436*eV gyro_mag_ratio_proton = 42.577*MHz/T @@ -219,10 +219,11 @@ def __init__(self, config_path): self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) + self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) - + self.CRLB_constant = 12 self.CavityRadius() self.CavityVolume() @@ -420,7 +421,7 @@ def get_systematics(self): if not self.Experiment.atomic: labels.append("Molecular final state") sigmas.append(ground_state_width) - deltas.append(ground_state_width_uncertainty) + deltas.append(self.FinalStates.ground_state_width_uncertainty_fraction*ground_state_width) return np.array(labels), np.array(sigmas), np.array(deltas) diff --git a/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf b/tests/sensitivity_vs_density_curve_with_sigmaBcorrs.pdf deleted file mode 100644 index 7866abec64c7040c644566bbb8fcc89efcb8a8f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30695 zcmb@t1z45M_C8FwX$cWFARQvT>5%U3?(XiCZV*tqLy&HfhD}LIN-NzU-3Z^_dOSbh z@4Vmn-hW(N>zXHK*1Fd{Yv!5R%%V~h6=z^(WJ94UUnDJSMqwrekvbS#qww;QGASBa zn7NR$K%OX*GO2nwnvpVz7`YnRI#`hM^P`yAn?fsc{n0?o(^W#n)yUP1l zQr2JBibk%kX3qAc?9f{jCPill6BRR8QXPmLQE`ZVGf!7iCMi3JP2qo)BL6C-NcBjW zR4k25t?VsGIes1a*iOOa!4sOmS5LThIL0I_3DwOCy zD22lN-3Q`Uwh-1xnZ#`&w2GRUIG93uLDtOP!qt+L`B&FK0J*q2n;F@mcxG;7jZDyg za>~A3QXB5WT88VsdC>ACOUDgDd1?Cw$(zg)Hb{chG5~4y6N+Am{inkWHP);&zSETe z%GW~PWGK`f``MMd<-_l5YfINQFqn={cBpjk10HTa%C!1IiZiNZ1B2GY#44wrc$E9= zrhALXQ|;SrtX0GN^V^gZf4}P;f#v50Yxx-Z)=fn+QdcC4Oo=vgoVn{R3g`atzo*sAqB446tiBUPaVzkGM4E`K z+-GA?@`U6TSMMwANO@DQY^Yy2rT63aP?8i@%ro``@ z>gjW|`#e~L)33kPik#G&B^*ZaAPdAZmE>gLY9k-kTlI1>tl*g4DP(^vq8T_XTB&%v z8-pc%?Zj4*ogU_qs8d|VPCvh-0ZixA~`8 zy21}mtsh=l6MR#mF*2&$1Z+$ z{Z+PpU2(|Oyzva_qjdh%5v}o_5utVXx+2|n4!?QRCs+n0>jUk{fL>)(-naARM^sAI+l5}dvj;ihaOJdL(guGpSVbz^Xe{E(dKMEsDMWCN z!xNKumb9)|woNkYBk&WT8V>KL<0}ZPASEi#hVD8DRPnN)Zhpe>}8_zClDz z3Y{GfH$V79SlhW6Ts!u5unkJ!P>_Z4D-oGQ0)4rCl=BY|-DVs7aS87qNtfu2FY%p; z?8U$M;AQ9e&9`1f>brGl5xdE2>ry{S^oQ-|WLSq0{fq@19J1xc@j4D) zpEbqV@4gwg{=O{&2>f?5PVcKY;<`{bLd#_C#}%^Mrrv)&{zQ#&zHgM{zU1HlOtf>T zx)`s+<@Ni#_RBkvg%4e@ul3nOy92vP4z);Zt4bwI%#)#m4PGLrG6j>i zi}+>n4GCfe(Ub2SF!IGLW=*Y!><;jE)E}D$a=5Yz>yY*Yj)r1VJ=R)%A{5#i@6Jdj zZw35M!{hu<52^(2*JchM;+9_B&*ilHo!)D7Je-`IGBFP&_<-vUGMrA(JSLCOeNq~d z_YT^fKNcTe^ji4eUDzycoebT|DmweV^VZc_LFw=;ft?hf^5XIzbxxA)-z}#JSbpd# zdHGFKo;!*1aN~p3a)pE(lc*H@2486A9;3q&JaRn-l4aqdx1bcU1;|rvC7O_rZMgOu zn2qadu5w_yXu*HzbTEe?hG?CC;ouMwth+s}ip;@t(vZva<8*|G!f%P_;Jluu=V4+8 z8%2eb+OaMpSSd|ZbV}9{rG-WfUjE*Y(Gh7vB;YjDjGCxiHlrP}=20P%KgGqO6T8wl z2N_sgLsmqZtPa+Yk#5}7@aWRxkPXrXPhwkAcKbVYfScWk_98WTRKBtNfQ&Jwyca&N zmp_F9Z)a9?TjVa8IFE-XM^@MpN&}fbc7kEnkL^Vus_f~oI15e8R{6WQ8ozCUf?tYX z0>g?GrCGF@q5p0&u=~Ouz)V|q1)o~M;KDis|Az8 zMvAnLX)98vjXR4O7iWRl;dRu^*PNq`6JoS$8Y`%u7(zPY$U5w=colzKjBt`vN%m^G zT~YTKUq$Kzq|Z%knEF|m7jh3i z_BFp*Qw5nL`SLje^W8(|U3hn?QF5@CK?d+S#n|LBRfNwvX=-X};iH#X+{eU3!)fN_ z_{wN<^=?s~x)ys)su6`!T0Iot8|x9Yl`NpSS9>6HpuD_FDm%%UgMDL>lx$A~ zMvsS0jZv@yNhIAuN0C+ux#ub=5bK|x*#I&%8lHy*44@UQz@K;n_9&g+GMk>M8PUO~ zL}@dr^~esJ^h|<{)^V5x`k5OM?-3s&Ty$3&Ty&kUDp+=`K&6%NxD2la^8HMfvroIq zRVx5w5hqW_zoT5DaIYZ;>En6{#jW6MpuW}HudY2qY({H{UJ~Op;y`zKNBA0Kn4jDi z%3U9&kNJ_9_0z@+*R>v0$5qVZR%Zf28jLfhk|8^kA6Bpedd8oyMff7|M3C>|a`BJD zoZ<*lcwP`*1o-r28yOa=c@3iQSQ8G_)F~Bycy5fw=ZgO-N~#+q-haZzkUMGUX{j1K z7?L*FO`(nBz`+a5~+=8G5M=EBF`;JF|gh*U2tC!5MR@9w6QzkOjhH>u@ zt*KZ-clHX`lXNvYCWbN~g19jgc6J8CPGjuden8vU9n)}317<$jvwC7bT$kJ`^Vd-f z?|qHuhZ7lY-XyAe<2gT@1uUn*6JWf0T+hRtHNfjfZ5M z52drb*pZE;T9{gHT=yG_*%dfPsr6!us+tcptB=8!DBi3t3|uKS>#+q0KYX$1f;>=c zffCkfI4Qx@plX(g%E;C+;^PO>LpE^2I{y`-uwbMmkQc5+R9t=V80F*FycFFf(quj~eOwlYU|0YaQoaz_%h0JX`P6g=?Co^6`!VKQkqL6oz%UT3x1qMYkVfYhwXr0#s66`nima0 zpHkuD&_3FNNX$#4Ot@ay1t1>gS^ypuYZ~4>9*QGf!4`3mWE`S@CIN!bxHRgALw2^lW9(zSrWxFT)&>h zAqlJHONTuO-bJ~p+Ny5zN$X&Bt<#1~6Z4&x$9Lzng*lMI{=1T-*B5P1}SFsDfBW z*CilkPKnx)Onu^=q00~w0uY1T#-kAq{a(+6vq}(Wxp+*%lkQ2{$-8jzvtCsu*4i%T z4aWMlFyJ?)VXT=k{QVaa;in0(%GIqr>Xx(-gLR~qaMh^|Sw}EGjLQNMq#mtlnm_M& zb-l51P-9CrCtxs^XP9Esbcfn_T|AjaYN)D#I6(<=R#dK7OimPH&!k`-6C+ zDzwZM;K_VOO`ySQ(OnUfhE^H0dGs9X>Mi&SntO(2L`WXI#!eZG>d8D#MOO{GX3TKY z)5;(qvvtdrQ)pSWcuZ_+ORw52ANkuy$YN!ru)0aTuW>5S&0(2Y=*#mZEV6Q8QOgYD z`eIDotLO0_)1{7eS#SBGxq=AG;k+=Km#T5IXy z7u`}AL#3YTxUdtOXl2e|0)0*|^^M`j3C3%);8I68&&P$A_FC-7@qv8NW@^u!R(ki0 zXM@$46|ehE$cDkCt!6u%yu6x8R4&NLB&B9wC}oP$2#vJdP!}+p3&!Bp(T=^^jTXhAI%4l#A17 z1L*T#&NsK^t9fEs!u+7Gfa8WmlOmOvw6+Mv&bO&;9F!uB@DLaQmnBm7Y@?NNXLL_LLP~sx;&VILCNqz z(^>A_%T)gyTs`cq08g7HO?R^gFdd92TI+-gMq}stwi@ldWZu9DU5?@B_GJTDdL}c? zB&kGl%~V#s^N6ocY>V7h!rz_ZfS(XE!=J2A-%%*M*cr4>P;i%NO9%tkogu1D@A;hLv z&GO1UP1vfrGXtR~Qewh05Lb})wZt0W)pH6M&(==E-lyahyir}z@>{bUu`gN$uPk;1 z&pcr{BWh}dbNW!PzC?AH#sOF1f`&go*00`&$0kle4aRY~hCfA!1A=IWn|gL^S_SLH z6*c8`*8tftjK;m9BfumCRt?!HU!lWnkNrts*u6ZTs%)mz+F4R{Twg=#wdqfWQGJJ%T7yrINPo-2p|+0Er~N< zu+ZSHCQeqs+hFsKJtd^dkCV51%j}6kNVI?u1vDm?P9f~qi?d41Yp4gwtC%Y-KY9W$C)bd#At45_FF^aiSEhG{_S z&d&a@y>F2$@OY0duLGDgRd*+hyMPfybUM3Na6&5PKqktR*sXX=*h~{kIQpd3XJFd)yDlvpdhP5tDczy#5h=%nOq5H#*J)r> zUeJN2en@Y08n=qGC#*-%w^a8MhNU}QX3qrpCPblD?$+Gik>|z5*TS|;=sB&+XfZ0h zLqHWCZ}(B4=L>O+Q6Q-7b%ZLR!MaVbgch^>Dd6oLT}H`qP@V^eRyC_<;4;!os{9

P#FZHa*wPtCRp^$jo0k=Z*&?zTx7kJPzD$a(ioTF)mm3EF%P2h1#btn5(fWGd zlEA2aUm9jUKh<0R08-@BGklTPvKPr{NbY8T~_ ze$8$G0-5l;cN1*7V^To~cU_X^**eL&Y&xO@ODL7;3|wLkHfox6fRE`P$ddEt;NU~9 zKd;Zi2?T>_RJ2BL7fBycxVrJW;(-uo)W&VWh3-9bn{z>n`tEnU%f{8p_M1s}GmhX7 z!vuar7%l|ZB2HrdkAYg!3hFeFpzsD0s$)eIp^B%}*jKFtxeyJD$_PnJo)T zKf9D3fd%RsAPh^4;s!taI2d)3&TxcIps~%XQ>~pl>}?#~Z?@GsBZ%IZ`*Ck57O!zz zG~vUl*)m)upB>zUEi7kHP2&k&6a!Z+9rc%NhB3KJmL^(a^*S^8&G#0lF@!NeBbZSP zjq>P|m9H7L52W?PB-0sq>U7qIag!Wc(L31QYBdb z+^TYUKkqPnTsLrqg)@ps7fkguCPM|F0PNQiD6I(!HOoZzF5lM8Uh1^0fdgCVfCF)4 zb34=Mv+Fcrx7=vg6S_%wZp`^RqXrppF{<-JlU8_*dKqMvoAN8!$FVi0Hts&_CTJK% zZbjj)npA4iB(Q4mYCX|1x{JZ7EZUOCxju}c^*&5Zh=VH??CF|n#ZKn-7tZXdGIVSe z#(`(@n3Yx>9&9jqgVG>M4dh)icKSP4C!ogI3%jGQov8sX>6z?W>hExB@r|w@@lEoFj(t?ip2P z%_%MUFSvMOftSacLLICgfa<=eMq2T@Pf-J*kxfM(2t#Q_9fgMKI=wb5x{gf^kYeD9jMdgd_;4FBJOodC8(rK1$(M=b;@ z^jMK=*c>BwL5NQmiSEZE8Z3G9R)QAv?q$RBa|^uJqneU(=RqnXxJPtnQeSF%m-D-# z%F`J%<#aWUYs^vEP?*Q;c#W<4L`D1287QYB!^cg>o3=_XRVo0n)Kwck09<7tFvtLP zrJo3C_G@tHaO^D#Apw2k3{%m*3YvqcoBXHV#q-cm%1qZWKRGG%V;(fNeTph+E<=OT<+lBXq^+Yc8Ocbru~5iPw> zSjPJ)9uTh2w?)@DM{`(;_ZXL6Hm>XpGA$S@nFmhL7=S}9m%kx15HJy~B~}G=6FE6x zsaPh=A{I2-Nho7RVKum?Th-%f;65;UPujw=B0iz-o3aI0B$AaA2AZRW%{I79LEL|$ zz1B1pq;?;FtBi_(%r&+H6oEW5sjy#2NDQg9E%j-{n_1{;3`C7Fjd|;>jY^~3R#ys- z-YCi2X7^OXnv9NGFq#T78;``f#gf1S%PS-4C@hZP%tPh|tjqhN`}uT#sP=Og((~ou ziY!Zu%-tri=~t^aRTScj0n=W!pd0i{?e^OxG!&se=U!eYdyiT~*142Hj^3Qh`|+z) z6eGFEX3X~s;$t!|v}Ntu8rQ%U+u|BmrKX*iIXgjP2_&6>Ag@=CfK3N&yu^%so|*NX zM_%=KZ`8*}@EMK0bggP8Q7jr$?dc{SGoD~KSSrlszfx!@7C>p>N*3_k?IE{l0V&IWDR_&kd@N4+BB>fS%ab(R+bI)1SQ zpHH`2pJOz{yNbUUqGZ!q)>`9Y2wQeXy6&w5#EBByh-4W@OEE^!`T~zUnyxT?u~%Pws*en6eq4mrV{P(=~5zCs)Jtj{zi>B7EFL#zP6gA7{Se z=;gjQVx#eDG*nu&^;ruKLM7;onET>XOIW|}n;RJ2L%%iuHg_dxBllxww0Sh6*cshz z-dxbu$~t=xn{KDf%7&}KC#|M{up^h!dc$$Q1DnI>BGj5zH7wKZAoQB4?MYl)q~%7g z#uvKswq6c&N{(PEvc{~P6}V)s1|h3P!=tK?yYc{Fj0VW- zD#C;EG&=HQAkzJI@yZYv_NbnrTWokV*n6Z6sMQVNZa>?(U~_>5FghF#k2#W2bu--h zkta~aRAkXH@YriZd6IF`3(0MhwU+=>ZqILVS7l-4UFQBo` z2Cw(NOYsf>Qr1d%f{A1?T!05k_!in~lNODd8OEO-$Jb06i+7lHcn5SEZNzmFAy^pT*?qaua9`+ ztM^{ovVIvd5`zG-xOk$~Wvi38Y0>j@c25#)5zHm#fZCu@4UMb}^XTV%1iDyT>n$>6 z-^J_1@~d>Bo6;E!Gwk-)7}#`NFSgGzL>o6q_}K4=r^rfj+G(HQ=%7va7)>}CN0SjX z=L*&VveAy~vyw%*s!(kta*R;9Pe^a55*S@l*jv~kORG`$L7lP5iV^KFdQ3;}`6R@2 z$c%QS?v*Xgg0+SBvs1#`{ z+>MyhXebPiZY~fjj=CwbH!tfjChSfVyM7rV%SiO`!yB71@|tolBKmjZLD)dSPVLxn zOXivSnF2sX{koHIG%Ypk_rx$^RHKK*AH)e@oT!>I33lzVgQQo|di{hf!;@W-DQji5 z)Tq~_pS}~y;4WU)BK7726b2T3q#3jw-|PBx;^MviL8f!&=Ne`ajYVuHG-7k_5*B^& zF2?2Xnyb5ae!#gXTCL$2hAF#{u@Afbg@AQ?X9NZj>p@Qehh|ZjS9i>?Rc(i?sIO%dXVMZJbk#U`jiWtAW7v3bO8ZpXP+g;m zXKhRv9bdap7lA?Yv2*-K5mdGpqi(x#LeT@1(sR)-P#AcWULA@}S%O*BCrV$iSb}@Z z=6#shbULkAsrYzxa;pwE5>XgETYDF&N3ELUe=uO2;6{?zT7nNTVUarvV_EgkN^XYv zoxs{1%^suH&4!^iUkj1b8Od#Y_ZVkXU28ak7^TNu!JareCy2U+rlVRCCjg*WLlC`3 z-cG(F*I}c@He7Jz+wT2sxIiNLmTLy`h}7*xSK2kvkgT@yyWG5Jsh8FVs(n%|kd1q? zm}jT^Qk{YvFiofV!s}5KqoDyI;fm_uz+il1=~pzpcr1qn7B>B(khO(<^) z`C=T$W(9}JIADac+yo0pkCe2Vp%R{sP0tW?yk&!;OY}QYwU6LDt`nS9ou|Qr08;vaPRv3Ig-#}9oX)b3rHWdI)FKQ_ANCy!}b>!fR zqrAUH6=pZ0R975zpQq|#%{?7#w(Q#GU9?j7pD#l@YoHB6Gm5wA)9&~Z0)B^}(9VU0 zptn@H#0k0U(>+BO!3)iMW%rRe1nkP*JZ!f@6K@^vuiun zfo`S`pX5bLXqXu|VR>n48ia@9Hw2gzw_x^F_rAmUmi9WB;l@qnHA3{DU;phQ+GE@_ z0}{zEI4;frfIE)7j_4Tv_R1`u23nrdg^!Mez^=b%&dy z#Q4-nn|bxIwbb-2$~DV0EXw;=@YQ+C8Z8y7^ZCG0_ZB7qC$dEWwY%jAg;TKcxl*8k z|ECnj*xfx6&32k%2Rz<3mG~{a9F5l}P8$j%A4MAVvaMFtrf9-X$O*|nNedpTjGuro zT|9*kIZSt;rwaZ>avhf=DF(h50}6}}&eumd(}+UZOa3PXjtsRSJ-$VS7l;NG*Pr7O zGw+Yi-~kV0{sCC$A8#1D?i)T|UWZcWz<8hh2tNG5{=O=1w1W3gPrL0<4}U4Qt*7>O z&4ulD4P#G-QQ&Px$7r2@(+K`mGSlHHDapflih}zd{@TczUlU0NHqo{9IOsr2XN{OG z5ixX$zddoifBJjHvOpDvNO~R`$*qp zh>Ui~9-mv}ygEKUm=xIc`zC<-Y+clvI2|Pg32t-X`6%Mr!`+Wh1|3aztH6#%-!tNl z%Zp9UyO_T2V@v;)3uIzqoa1@RC4r-7O#+#94}o9qnxj5}ESi1KcQz<*zaU!7X;g7j zaQkqGO-mm|>}|zeE_K}9jZi}l1RQHPY=!iLc)hDe4jUbs?`|{VI&N+l4!s}XK0D2H zY>fjSNYL?3L|U;9MusdMEZSy*43f?pdeoL~E+60?$RDA~W@aBju>@kJ5_0+RV zrDGX<4ivf)wG<3F5UV+)W@&ITS9@0T8v-}tE3~lMf_ceU?_{%ETal0i?el&rLNu?k z%6H;?75jJ94r($L-kU+<9~sny3ka!G`n9rbjoaR6pZ9(3fZ>BwQ*WzI4w^-fNyYN0O!BIY*A9`@4<5{W8&-a^ggQOi<~%T8Ojn}5#Uozz z^;(~TeK*)F{0p}V#fRO57R^i#i2lzeKFoeKL?-Xlvz8d8c)xst)K}WtYIAlb9+0fy zNg0DvU}Ay5{o-ZqyDS~8q@i3OU-tT!vU=-lg!*Z9;j-Zo<~c73i2C|n#YNsBh)Zwu zDn4ktQL*nPY!TE*4ozf2cyq6~b1ggoGJM$8HC~5JQ`eLFz@u^P<58)B`r%|&=SD8a zCQoTo<72H$jCOI-ON?DQ4r#hHrHwcCC}7PbTx+vb7B8#R_DSJ_;RAc!Ted-I%fwS0 z)=!HSwNKJhRwG&y1T-JCPXuoVr~R@!@3vdzPsbG)mweBUsUL1nt}_H)-HaSEk+fZY z7}>aIU~-hY2!AV;W*nmr|?;OJRk8 zcRR>w5OdUIi_rtDF*#C@N$ zF&n}wl%hG}@l_3<5DxOFMZcTZKI@25F<6yH*`Gi}kWp|hkr>**%`>_#f9N@U2a04S zexG8#weW=~zqjf_Qd%crspiKXet*te{Aau=QDxahG}<@XGT!svk%)VD=lTbiaxu(# zs-Egx9WZ!uDlv_=$LU>lVJ;`Py3cW6l&GYPCf2+f+@-5&3&;a1rhg{zK0r;Y(Ew^SUw zKsUGddr%J#=PSAe{oI}lIPR^eBvQt8x|$Z4uUQsx@(=Mm=e~B;U-qXkO4L5pQ`FVY zv1>*j0PZ$5%ISmIftEEb0k630c)mbdZ^D(B_4QQEf3)rS9wd6d)iiMdN7VovTNk14 z=6{3Lf~xubWz+2xq~>?HSGbSB{RU8r#Nsd0J$%J&nU;@hd9%LFCqOFVp|e~%&7mgF z>cbR_W^H|$L#?0H>pY#lCbBH^hlFA>AKzlC9auc_D?pAvgy<^<6ovs+_TBm~-|qDF zt-e`2t7(HV$FTUGRSi`+_~x=i+?LTW8>L`IAw3474}-5aF{%a11?V>ZDwgJxiu69C zW@ct#Cg$j**Xez}%frE=TLU0R z$qPzmI(VllJ-e9>+}OLlcaN{%IX>>O@<$IUU0R|>U$U-j5I+o>A z+9s!yO^eVUd0c_>VK2NH*F>0yJF zcaQ!Gw>U@e;db76H!HL6$VWW8oe|!~Q}V|W&BAv2T+{s8OH)72QPac4lmCX%e|jjM zm6h$U;rgb@35FR-%tcKIc~$(p{L@+s@8qvl0M@6K_3 zPM7x-|GK=NfZ;4YRK<3Eta2@Kwr$`sW&dh~qGyhoS6D+?1KW|FwM(_@^PVyfw>zQ9 zt6&Z6WI=&*Y8zhOsZExtqGRQZ{k-Y7-NjD&);J_`gW=lERe89d^ETH+Mj{%g=wFpo z8DiH@kL5pTV}ML;U%16kj&Uv5Bj#m^EA=VK7ntWlpA<)GXn_}Ua z(T3G}*$e|Z-g)D4={iC>Rk5W{jPh!j)0IKgwl&;}G#Shizw3x&%2l~iDJkz)n@oCF zITOLBkdh$jv7XtZXfGXMgIb6@8oT(6;~B$1UbKHr?%=Inv28UH+o5zT`UC$t0mtFz z{3Gd9b@n|+$wgb-necw|#(_rn)P{y=-^Z}|T+-R1v;e`c=#1^(F; zrO@G-ky^HJd~=m&-(JS3o{!x>Xp5MmW1-_!<6BE53w4m)k~&eJijfP4c|6t^YL4Mb zRLz=Aqx?XN@=ewl+q3|GxXdSUni5PMLRs6zo>&oJj9A)yU6dh;0m|VU18tXAv(bkp zKJu4heNC@opXZJ0U#`2x%9cv_u!OV}+fGs5jM{V@4ENF^o?0~RZLr@3?t9s*5F_=Y zw0x9iG925vGhzMsDx{%px}L>Hma!(ew{AI3UZYGpehjlpJs_6FD*_fWysh{%+i+e`ga3 z*~JyAloT>Fpl1L7x))MK)Xc@i*~-z?!I_l#XA0J@mU2d}&Q?E@l^8)FXv9GN{(S|p zE^G8JODyc{q)hTgb`W>|)5Sjm1PYh+H|k%MXjqxLT0(sPmF)%jEd5@QKymzLP{_pu znhnLx0`XhK$WhYF%EHnW@*ZOC*Bb^FRtPhYWG4$qmKMi9_WS=EvJ@m=&B{c`-on-l zYDdn<^CwUUPIkzximREOI`sK3{-K`yK_QdozXAT?57a@ZKafrYf&Om@%Kxq&R%R|z zHb{T6K~@u7tX!lVtQ;uZ5VttFK%^|3?4+#Rpr67H!3lj2th8M zJEY`-kOeJ&3JbIz$4?TVCDi`UPJeJ6)f&>(_)xWhsL4iOhKi~aJ0XGLXWOV)#XdwKG0cafjReo{} zi3?~<{2@Q%0U8s(QK{zkYVHh#th zWE$uALw?7^?-CLV5PWPXKTGKKAM&%Wf4zs+`L{sh0@|z4@_#l}(3$`DWCxkwe`Tja zrg-L`DW`wrc(VLi^S^BYxS9X5oMn$`dE5y?67?x+#Jb`NxQxtydU0I}Utu1m?kyVy2_CT{PcWaB{yVwa4%R`O?{YFx|{do;X{}b6FmIvEaf3s zoRYsM{`(6J-@#qBp`({t>%F7WE9-3$DDP;yP^=I~oz+>v3{lXA2gJu+o(n+5t`SZX-AG<2jkU*Qzv@-21BB);@ERLpvxj{gwWs{M?lE*L;h8 zDP4Dc1v@JPJh!KufCO}sm#v7`Bp__IX?TJ&cH)YSZ6v_UFd$ zzjZ5Q7x*V-thH=vAjRr>m?vS$n<$%*9S}{Jt{{Iz(hvZ92(}I_fKAg1T~-WT13`lT zrX%LD`rnxTr#mFb$p4FJc6N^(A&9@ zrEvmg2nTccKS4kSkriu)4;-|Hc0Sc@FWf{|8K;BHmbFb3{t!hF5K7&`rp z$hc@E1@oav@D4#W)NY1)`tUfyxcH{)3on7u(&o4w>Ca2BCn%l1iRQhOlg`4ZpIlfD z-GV>gA=>{NgnzD>{~JPP$V~kgi19K{x=GPQE;EHCU>*Wa>nC@D7X5XG(p-a|nhY_C zYPjwvg@5WxjJ>opQ3zqs-Ebw4SHX{qZZX%(9Wr8>bZAX)>QpsK__CjWgV&mOT5cX* zrN+t}!kPAjz>wC%8RPu?J=ej)n|NCdqp>W4cXgoQggZV=6_+uWh=e-|17{nfv9wJf zkf98}!c2C2->B4<0c|%{$Ybz)%DeIugmridza+$O`Zq2^^TdD8O8?DePL{uT%vuLh zf*D{0hZV&67eIV(Wb7gPK9NiQP)5woo4M@McQ2Y# zzxgjvQ|&|%&f$t#CpXJ#sSwAry$$i-IdZJYI(cb3YHq%)ou3l9k+?O6WBfhecWzi* zPm8*DH^iLhR*k&{}%g5 zCa%(w>~y)xewxv+RfR{)*i_!R+$vg4{!;PjWK(w=8s@pJm=(bUN$ zzvOU=I|W{adMrKAq5I9i@%zKb-0LCy4S9d=2LCI0kcG;ha|b)*i`WA~Yq`a_>x7y9 z-b&q@8-DRHqdfQ&Nsy|ck;>SF0*;CKiaTQQ>J59r^H&zm8h7*xp%Psc%TiARQ&#ld zgNB~Kp)OoEh za)NF7ts_KJURW$gg26Aw(D$1TELPRXEcL#=w=*dgm;N-_@mNmo4brtJvPA>iciemb zQOKwF7DJv)73*&Z`=_fM7RV~{FN7&@zj;K4Hslq`4^qQh5bSvWzJX)*+Q0ctntc2b z*}x<=QJQBO1x2;_rv+_OQuAdM+1`$edJOE+%VlEn#AsBbums2H@-_SH*T4t&q)|=8 zLyoH`Eq%<{F4<`fTnokpfQeOR5BGq5j2?DPtXsu<6-^0H9$JWNLw6#ni|V_xf!8dX z+gu6M+0lm=3-s>Xuh92UIbz9hwytQsgE)fo_q`6`2%Lo;^uAsZi?wW1kv>DlomMjb zp1BcX(B|$+{NcqD7vXdvMr^W?9oizybj9+zf?Q=K#}fYc+vPd_8wR=zf5X>5UUIQ< z|Aj9;#h0%|$IhxnZPwhJZ1<(b*O zx+HaEDf#PN*4H%Iv_3hWxbz4896Ho*_|{2Rl$9d~3Ud+%1?&bBQFzS9AJIQ)S~kU& z;~Hv_QjWn-WZ5MyawqpI=j|`x**2B?TuzZTH>t3Yc17s;(O}+GKIxMy@$gk<{pVnA zoaW25NDf4~*!k`KOKdAzCgm0)?W|XBPm;dkZQ(shvGB)VGJUv4ybsqV{~HGX=>m)u zvN!lM;`o$h?0W@~7Pg-1=1v@CjCv`neQmvUun|!%p>2(1?gRM^#y4%q=sj@?9r&nU zsl!|3%c!HB<`vNr9ZSXEu$c0ltU=F}v4)FC;sli?i&nIFq;x#*N;o+bF<$;C54FpW z`+kin06bIuxN(J#MW~u`lSa7DUI{<_i4d02Rsv4(JH;HWt|g`KdzUN#YloOZR1?B8 z<*l-7RH2zTjRB`mH4Wn$v*p^B1%3N$Fz#}@GHpO zyhxU1Q17!>!8f=|jG6Lv1<;nTog|)&snw04YDRiJx!}H&yxN+!B@5%RpFWA)AYtug zdGgs%hTAYNWldsvLCX!RlBlWivuhwp(zK&&{obqobA@}?1?>^HR%fJq!3{Z~qZaf^ z@tU3<7XGYQ8t;7iYJ&7uaaOPqCf4+2cRxJA59d})w)ZyV^_t`vAB;s~qikLH+8#~I z9`K4p3>pv zy3J{KP7#KaMky+=HD>*PnY4`f9+GTSTI&6Dbk|{RUu>)lzD~cZy_JjSWFgQ z!!7jhApq0{FL4q;(sO+rO+;yvMa4M5wiRcFfkgm@Bennhm}+M7q=!|4lp4+AyC+@` z=oZi8i@nB8vMy0gZYg;G2IW6pIkU3;_4_3TnN38Qkrp~s90Mg+QsPhROQ8!@oebG8 zBg@E!ww^;OMW%z*O;i+=Gl>d* z#Ea%|w9mhf7mnmBjSTj4_haT|<22X$Org&mdFx6xA102F5e_{l2GW~-bw;5Q(i!vE z$6CT5stlqFGOD&=3_dVpnR>Vh$JIUbyEOj97=T;8)ec67DSnS#59r6A|IR^U38U5= zE!9`tiVkw_jG)pXY9IdwlkBIrEfyQ~gHBLMOYpA$i#zxm^MIry z?k2+PHb>sxG)Eb(J~+XswpBduBdXa=CXLF7^dn@qW>cotuI%~EE#bFF9u+X?`lj6# zWd+5_2Rpn$7Zzg9;xSXvTs&lNv?sWORwr_awbyhxYgX+jYML~bzLTtEGU;CI0cHN5 z_O3l1%5UrEo@?b+5(!j9`&6i(rW82A=mWob=2>b#B2cv_&e|75RPoS`%7)-;a)krU4M+?AiF45f1f z75apaAdfeFZgDCdI7F~e73UDv-I&z0yT6zg=j^u6=xaIge+&;4|^^crWZgZTcSgC~`YixKi{% zu0*WX<#QX$^}Y$ zSv_v?rpIs96BDSTZaB&&-U1;B7XiQcOJeEEH;cR+&g>=Ue0(&a?e%s4*E_VlBCU=(^;d!3=mbE;azcHEX zVnYiInsJq0=~0j58^Us|L_#NIT6!S;n6i`SRiEeTKdD-~dmlR6M#-m1-pc-Swzkii zoMUM&IgQOya>OhoNQ*&u5bj{SRDrxko2_eLNuWpetR- zLDJJHWAF9Szg3o%9G_;{FLsnk<`L|PEl%ywV3%pG+NpyI4|WvaL)+lhmNkCExvrw& zMmf`C`V-&QJr>n9mB%K>${~E>Wvt9zZa$V+CvYU>9mxqNBd?2yE0P&+!NUoJx`m+e-!k>F02BVlT_In($QLTtSQ#uAadHrsYaUKqM$ z-(LNJ{p{Bq(=Q{-;?sum--Zmbux?%L!bG&ih8!qhUgkYL6M{f3lJCV&o)eJ2k7eo0 zxOM%}Qzr}h(jkS}?XEhaqQl$O1)*}_Xa0UUPS{4R- zH1?(%OWowyr~mz_u$uUGr@*v^(-P%zP5o>mrhAKh`dNmT?_O_P!sl3AP~oL+&yKD4 z6M6p?=@=Hf?c#2uK^6|7?|)64{74${J0j{l2(wT`k62pPkWa|vnqI6{54D=T8l*@+FNZ`fKsl0 z$Hb)3@6+;p3|#_`NP;>ThpZ77aMLe zXfFnLV*}X>9))PR^>GnqT3(%3AxnNFpSl?D;g`Y^RnY<)s6bb`i`;srIJC?DV8O+Ny+B&C8lq{ck}1#gr}?DG2b=g$+k*;YQ9o9hSE&X?7<}KS+hvU!p3*@Ui+$tv)ZM^wDy%rS zvzlfX(mc-7fp7T2ZycyH$mAX`HnwA|I#o0^BG*HFFDN{=>6q_ay=nv0io>_3OJn17 z^#tt3Q13#*43AksWtX1Tm7Q(Y41QCX*BngKf6=)4^uy@z@>s>-qdE$`s5-T}z>&OB zNy&-)=B5&^`-sj%%Qk&$JLN3ZQ!n=1UZlZZq}Q^@N9yX)!)95vx$B?xgimYKQxw@1 zkLk7g@9(`>82Nf4R+KoN<_E zTbM_bHyfW==-m+Y#pxfgarcb;X=&IROtR5CBOQG)cb!^#R#Wo^p0`n{l4N-{`wuDC zsATco-j~ywvFF3fFa|Dz)%wNB_m0@&OQ!hDTy7qbrI9n+a+i)2yBf8&60W7)PPB5P zo)@ahIz_v`!(>PG9?YN=L4-Tk!FtbKqHwYU_Y1-OX1mN%{KTgI{`_%N^~4m50_7NG z;bQ?>wK&Vou!W1k;LN~U7|AM_lM^y;tDC;d-mK6Zpdif~4FH_lypBi@2lp&H7Y9x! zD+mQuz?}>LTr>NV$)hhY88w{JY+64qS|UtSEZjz0QKrblzDr2mG8rMVu8lUHJBOr$3#8S2EI8$4dTbu6SNR!w6Ys>y!{7v_=4v7t{g zTc){Acymz$Yn4k%`|f_bdy=7Y5qo$`u1LCn-Yb-lB~j2L)`@)i5NYO+@urBQ?Y)C@ z;Kz48*us0Khj+!<)_Q$RY&o>_vD=im=!4geBC-D4E*A^(6Y({tYtr`N>-T*TA_&x+ zcD#_Hz~|_;;j&+3SWRnMNw)p{21)C$UzV8<91CUgAQl^KV&|uWFgV;EpsshmCFQ&2*FKWwk13_d72!O=42!nrnR5l$ZQ{N`T4EDp)uzPAM5eUAjH? zuNBq?Jxjc2Gi5TbMaN-aMLf^kMi~jPOM%2fYG!0j!`eP?^gu9xYWQCu2|20^*-dV*PYWKsAfWYo7C?jn-=Gxx6ZiQN)ZCy(grsgP*Qep>l5Gram_77L1-$!r!I zc40u4eeP_qrUVjL6Ab?3IS%=gUW{~t<=NyN{@hMsHgBqFysEXi@59hw^P5A|i5m&e z1yyYHwA8~&A!arFi~1u_^weR zO43q9Izy)hU0>I~I`PrRBA2}<5{WfYhfk$$daBr9$zdwqjUPTUo^eF+I_K%z$%$up zBrL*2PiT#%Z##SZ+bVNQ=<)Cx1Fwj%oiYBt0nhdM2uBrscdZ@6Y(xq5KNr{S(h87? zO(N7+P{&?$93hkZlSo(mAlRsROYW+4n;v%H5r|Mlyo?GoJ3@Y#yN9c)7 z9Uhjwn&yy!<4$ab@2KCHdc>|J7;#PSERHPv zcQzu58nXNB71R(#(PKYtJN{tnkY)bo-grq{Zzb84i2OBUUvX?JcM8UA! z9N#|1PdAlo)SosLm8k6k=va|k@*cRjGfL~=Jc|$z% zZE^pP6_v3r$6N1jt&nl^Kbmhw?8@;Ls*}KSe31K)dZ}vpt&6#?&y-`kl}lnN<@|S! z(#$%hFXvS~oZz^9NNI}6tQH#qqCtJ>JZrU!AOI3)L*t+ba(x8iuu0~|mZ>`}5sxh- z@7jdih?h$KY#Mh(>YbT}U$RnaMOR1j0p-5L8;TVF^3p0qL2ON!%juxI#H4bT6g3v@ zj>lJaX|I%QZOb;>Sk%R?ai)y4tlRNsz`0VXzA(sPqnLtLORlbvs0U3p3wsCTEnc1* z?rt>8zMr@%fRn`nzow(V<5PmV$%e;N5p-J~_TegSA>DOq6OlRXLW9=If1w52?ytx{ zz*eRD6S-r_5+U!0tWFmV<4bz$Jd4LlIQRURV!61@oCz)qJ=ZsFWde|x3yc5-%k)!E ze&Za})OZ+x5uk7e0nS}7xOM?2uYmuFzTlby&=3fUv#&D=Ld;+g z5F{dK07@ef&fY%u?x0*9LTEuaAcHoK3V=EoKmZ1bFa`($2vpZ2c7i%~2w?#c0E0b* zaDWhw5CVwc42r4&w=0AIf)L?yZwPS^Lb$^)4+IHF1)%^i0|5k~0Kp)H4@hH!F^@p7 z`XD@f+^Iw__amU7cp)MLfFb@MLO|WzJVXfd%=mmb0~!ucz?m~}%YP3MGAF72zav3F zauo~*zZ0`a5c>GP$`JogNDvHqM(TgI696X0hy(#oZY}}@9NqH=0iq0a?O#BE&?g4Z zPaqhD=@=ymge zwiBRvW@r`=aD{YxZ4YoqGc*SX08r>H90A5@=E?~?$eAm;%cTEx#-)L0Om9Eq&@i)O z(H$VYehCam_lY1JqqlMe=a|i0xk1XaBN8D{22AheAf!AyqB{Vb&G30ZUIuH$p~oEn%0?&u@p|wSf9ZoB3ZlnLpaVTL1k@1j z;SVc3>MvF;)Xc$jT7dQ|7WjR-9>UQcq@+MYgckZ28;BxiL8J@V<_rN3IXlFkap$c0 z{t)5m{SrnGr8ox;>Omy=P#^%;qR(FsJ@;?3XjnxDXDSeJ_Qo)0A-j1(boe;^ZZPYF zLUi@82ayqdpg)qq5ABy^+MYfjJOpz$B09miS^x@zrTUGiojJnrPg{@>{L@&=hw289 zvKzhd2oGnn2a)PVbdm$_DmY7zO7tY@zy}#_)Y*&%=lUUm(EvZNSgf@i&kP6L5`j+i)WRDFbO6Lq1+W*CBTzkPz}DK_q7%0OuunLLT-M3IzW7fG6S#QON*> zOeYa35F7(5fv(JnQ~-S4xMN-uiZh8qq!N9J)FX0pbmK7D*@Xw7M^QWgcR2e2aWEEG z8W-p{4F)&EABZ$dL*w!A+s#130yy3R8vN=q@PYmp(m<%afDc64^J(BW_|1erzhyy! zVo&fE&7+~fG8?><^JqvU8VQth9u4rpHhMly83o(u`80qMWt=a-coG)y0XQd6)%k5v zDzJf=PeY^N7lnbQyx?A;&@gnCfe(X*E%3ayXdD6dAq+GXCNv-nBcBQZKse{OMPbpf z(VEvE3JXrUTtI_iyo`J>jFyo`SfDQ`faY8v4Dny{VD?=9>3tZ0YzoJ zr)VTX*2MFFRfvm z8ybyXI0mSVUNB!Y0k+WdWx*gp Date: Wed, 21 Jun 2023 11:58:03 -0700 Subject: [PATCH 073/262] modified exposure plot. increased radial power precision. --- mermithid/misc/SensitivityCavityFormulas.py | 2 +- .../CavitySensitivityCurveProcessor.py | 16 +++++++++------- tests/Sensitivity_test.py | 14 +++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 32dd4231..a9e1a065 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -261,7 +261,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=5000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index b025f905..40390592 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -482,7 +482,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" livetime_for_label = round(livetime/year, 1) if sens.Experiment.atomic: - label="Atomic, {} cavity, {} year".format(n_cavities, livetime_for_label) + label="Atomic, reaching pilot-T target".format(n_cavities, livetime_for_label) else: label="Molecular, {} cavity, {} year".format(n_cavities, livetime_for_label) @@ -508,7 +508,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" limit = sens.sensitivity()/eV**2 - self.ax.scatter([standard_exposure], [limit], marker="o", s=15, color=color, label=label, zorder=10) + self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.28*limit))) sens.print_statistics() @@ -529,7 +529,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - self.ax.scatter([standard_exposure], [np.min(limit)], s=17, marker="s", **kwargs) + self.ax.scatter([standard_exposure], [np.min(limit)], s=40, marker="d", zorder=20, **kwargs) limits = [] years = [] @@ -565,7 +565,9 @@ def add_Phase_II_exposure_sens_line(self, sens): phaseIIsense_error = 1520 exposure_error = np.sqrt((standard_exposure*0.008)**2 + (standard_exposure*0.09)**2) - self.ax.errorbar([standard_exposure], [phaseIIsens], xerr=[exposure_error], yerr=[phaseIIsense_error], fmt='.', color='k', label="Phase II (measured)") + self.ax.errorbar([standard_exposure], [phaseIIsens], yerr=[phaseIIsense_error], + marker="None", color='k', linestyle="None", elinewidth=3, + label="Phase II (measured)") limits = [] @@ -591,10 +593,10 @@ def get_relative(val, axis): x_stop = get_relative(1e-4, "x") y_stop = get_relative(3e1, "y")""" - x_start = get_relative(3e-7, "x") + x_start = get_relative(1e-7, "x") y_start = get_relative(7e3, "y") - x_stop = get_relative(4e-8, "x") - y_stop = get_relative(9.0e2, "y") + x_stop = get_relative(2e-8, "x") + y_stop = get_relative(1.3e3, "y") self.ax.arrow(x_start, y_start, x_stop-x_start, y_stop-y_start, transform = self.ax.transAxes, facecolor = 'black', diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index af8868de..2ffb68d3 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -46,7 +46,7 @@ def test_SensitivityCurveProcessor(self): "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": [r"Molecular, reaching target", "Atomic, conservative", "Atomic, reaching target"], + "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], "comparison_curve_colors": ["blue", "darkred", "red"], #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, @@ -132,9 +132,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -162,9 +162,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 45105a71fe9c975e936e9422a0ae46c1ace0bff4 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 22 Jun 2023 14:57:26 -0400 Subject: [PATCH 074/262] Performing tests --- tests/Sensitivity_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 2ffb68d3..39d38236 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -58,9 +58,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -162,9 +162,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, "plot_key_parameters": True } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 546dca6e3ccd2ca6f093590af9eb9bb25d0dccba Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 22 Jun 2023 13:42:51 -0700 Subject: [PATCH 075/262] added loaded Q print --- .../processors/Sensitivity/CavitySensitivityCurveProcessor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 40390592..13081663 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -245,6 +245,7 @@ def InternalRun(self): # set optimum density back self.sens_main.CL90(Experiment={"number_density": rho_opt}) logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) + logger.info("Loaded Q: {}".format(self.sens_main.loaded_q)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) @@ -275,6 +276,7 @@ def InternalRun(self): logger.info('Comparison curve:') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) + logger.info("Loaded Q: {}".format(self.sens_ref[i].loaded_q)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) From b18425e07f9cc454a3c89ffade782e36f468b30e Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 2 Jul 2023 13:04:23 -0700 Subject: [PATCH 076/262] sensitivity vs. frequency plot --- mermithid/misc/SensitivityCavityFormulas.py | 3 +- .../CavitySensitivityCurveProcessor.py | 123 +++++++++++++++--- tests/Sensitivity_test.py | 49 +++++++ 3 files changed, 154 insertions(+), 21 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 184fafae..0728ad30 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -262,7 +262,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=5000), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, @@ -501,6 +501,7 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # Noise temperature of amplifier tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency tn_system_fft = tn_amplifier+tn_fft + self.noise_power = tn_system_fft # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # logger.info("Power: {}".format(Pe/W)) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 13081663..f2ce5b87 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -79,7 +79,9 @@ def InternalConfigure(self, params): self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.year_range = reader.read_param(params, "years_range", [0.1, 10]) self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) + self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) self.density_axis = reader.read_param(params, "density_axis", True) + self.frequency_axis = reader.read_param(params, "frequency_axis", False) self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) @@ -102,6 +104,9 @@ def InternalConfigure(self, params): if self.density_axis: self.add_sens_line = self.add_density_sens_line logger.info("Doing density lines") + elif self.frequency_axis: + self.add_sens_line = self.add_frequency_sens_line + logger.info("Doing frequency lines") else: self.add_sens_line = self.add_exposure_sens_line logger.info("Doing exposure lines") @@ -161,9 +166,10 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 500)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year + self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 10)*Hz return True @@ -327,6 +333,17 @@ def create_plot(self): ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + elif self.frequency_axis: + logger.info("Adding frequency axis") + ax.set_xlim(self.frequencies[0]/Hz, self.frequencies[-1]/Hz) + ax.set_xlabel("TE011 frequency (Hz)") + ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + + self.ax2 = ax.twinx() + self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") + self.ax2.set_yscale("log") + self.ax2.set_ylim(self.ylim) else: logger.info("Adding exposure axis") @@ -344,11 +361,12 @@ def create_plot(self): self.ax2.set_ylim(self.ylim) if self.make_key_parameter_plots: - self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,5)) - self.kp_ax[0].set_ylabel('Resolution (meV)') - self.kp_ax[1].set_ylabel('Track analysis length (ms)') + if self.density_axis: + + self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,6)) + self.kp_fig.tight_layout() for ax in self.kp_ax: ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) @@ -357,6 +375,29 @@ def create_plot(self): axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" ax.set_xlabel(axis_label) + self.kp_ax[0].set_ylabel('Resolution (meV)') + self.kp_ax[1].set_ylabel('Track analysis length (ms)') + + elif self.frequency_axis: + + self.kp_fig, self.kp_ax = plt.subplots(2,2, figsize=(10,10)) + + + self.kp_ax = self.kp_ax.flatten() + for ax in self.kp_ax: + ax.set_xlim(self.frequencies[0]/Hz, self.frequencies[-1]/Hz) + ax.set_xscale("log") + ax.set_yscale("log") + axis_label = "TE011 frequency (Hz)" + ax.set_xlabel(axis_label) + ax.axvline(325e6, linestyle="--", color="grey") + self.kp_ax[0].set_ylabel('Resolution (meV)') + self.kp_ax[1].set_ylabel(r'Optimum desnity (1/m$^3$)') + self.kp_ax[2].set_ylabel(r'Total and effective (dashed) Volume (m$^3$)') + self.kp_ax[3].set_ylabel('Noise temperature (K)') + + self.kp_fig.tight_layout() + def add_track_length_axis(self): N_ref = len(self.sens_ref) @@ -389,22 +430,6 @@ def add_track_length_axis(self): def add_comparison_curve(self, label, color='k'): - - """ - limit = [self.sens_ref.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit) - rho_opt = self.rhos[opt_ref] - - logger.info('Ref. optimum density: {} /m**3'.format(rho_opt*m**3)) - logger.info('Ref. sigmaE_r: {} eV'.format(self.sens_ref.MagneticField.sigmae_r/eV)) - logger.info('Ref. curve (veff = {} m**3):'.format(self.sens_ref.effective_volume/(m**3))) - logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref.larmor_power/W, self.sens_ref.signal_power/W)) - logger.info('Ref. T in Veff: {}'.format(rho_opt*self.sens_ref.effective_volume)) - logger.info('Ref. total signal: {}'.format(rho_opt*self.sens_ref.effective_volume* - self.sens_ref.Experiment.LiveTime/ - self.sens_ref.tau_tritium)) - logger.info('Ref. CL90 limit: {}'.format(self.sens_ref.CL90(Experiment={"number_density": rho_opt})/eV)) - """ for a in range(len(self.sens_ref)): limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) @@ -610,7 +635,65 @@ def get_relative(val, axis): print(x_start, y_start) self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*0.9, y_start*1.01],textcoords="axes fraction", fontsize=13) + + def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): + limits = [] + resolutions = [] + crlb_window = [] + crlb_max_window = [] + crlb_slope_zero_window = [] + total_volumes = [] + effective_volumes = [] + opt_rho = [] + noise_power = [] + + configured_magnetic_field = sens.MagneticField.nominal_field + + gamma = sens.T_endpoint/(me*c0**2) + 1 + for freq in self.frequencies: + magnetic_field = freq/(e/(2*np.pi*me)/gamma) + sens.MagneticField.nominal_field = magnetic_field + + logger.info("Frequency: {:2e} Hz, Magentic field: {:2e} T".format(freq/Hz, magnetic_field/T)) + + # calcualte new cavity properties + sens.CavityRadius() + total_volumes.append(sens.CavityVolume()/m**3) + effective_volumes.append(sens.EffectiveVolume()/m**3) + sens.CavityPower() + + rho_limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + + limits.append(np.min(rho_limits)) + opt_rho.append(self.rhos[np.argmin(rho_limits)]*m**3) + sens.Experiment.number_density = self.rhos[np.argmin(rho_limits)] + + # other quantities + resolutions.append(sens.syst_frequency_extraction()[0]/meV) + crlb_window.append(sens.best_time_window/ms) + crlb_max_window.append(sens.time_window/ms) + crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) + noise_power.append(sens.noise_power/K) + + + self.ax2.plot(self.frequencies/Hz, limits, **kwargs) + logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) + # change cavity back to config file + """sens.MagneticField.nominal_field = configured_magnetic_field + sens.CavityRadius() + sens.CavityVolume() + sens.EffectiveVolume() + sens.CavityPower()""" + + if self.make_key_parameter_plots and plot_key_params: + self.kp_ax[0].plot(self.frequencies/Hz, resolutions, **kwargs) + + self.kp_ax[1].plot(self.frequencies/Hz, opt_rho, **kwargs) + self.kp_ax[2].plot(self.frequencies/Hz, total_volumes, **kwargs) + self.kp_ax[2].plot(self.frequencies/Hz, effective_volumes, linestyle="--", **kwargs) + self.kp_ax[3].plot(self.frequencies/Hz, noise_power, linestyle="-", **kwargs) + return limits def add_text(self, x, y, text, color="k"): #, fontsize=9.5 diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 2ffb68d3..cbc24322 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -58,6 +58,55 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() + + + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_frequency.pdf", + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "frequency_axis": True, + "cavity": True, + "add_PhaseII": False, + "add_1year_1cav_point_to_last_ref": False, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "y_limits": [10e-3, 10], + "density_range": [1e12,1e19], + "exposure_range": [1e-11, 1e4], + "frequency_range": [1e6, 20e9], + #"efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Molecular, conservative", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_colors": ["blue", "darkred", "red"], + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 1e8, #1.5e15, #0.02, #1e14, + "goals_x_position": 1e9, #2e12 #0.0002 + "goals_y_rel_position": 0.4, + "plot_key_parameters": True + } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) sens_curve.Run() From 75a668be026389312592ca6aa7f5e3bb55b10bfc Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Sun, 2 Jul 2023 23:34:45 -0400 Subject: [PATCH 077/262] Performing tests --- tests/Sensitivity_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 39d38236..146d525b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -132,9 +132,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { @@ -162,9 +162,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From f5aa6ade756efe3aff99f7254f003cbcb6c0fbdb Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 6 Jul 2023 16:31:40 -0400 Subject: [PATCH 078/262] Molecular<->atomic direct comparison plot --- .../CavitySensitivityCurveProcessor.py | 2 +- tests/Sensitivity_test.py | 56 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index f2ce5b87..11d8dd46 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -395,7 +395,7 @@ def create_plot(self): self.kp_ax[1].set_ylabel(r'Optimum desnity (1/m$^3$)') self.kp_ax[2].set_ylabel(r'Total and effective (dashed) Volume (m$^3$)') self.kp_ax[3].set_ylabel('Noise temperature (K)') - + self.kp_fig.tight_layout() diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 43c95d2e..198519f7 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -16,7 +16,6 @@ class SensitivityTest(unittest.TestCase): def test_SensitivityCurveProcessor(self): from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor - sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", @@ -63,11 +62,11 @@ def test_SensitivityCurveProcessor(self): #sens_curve.Run() - sens_config_dict = { + sens_config_dict2 = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_frequency.pdf", + "plot_path": "./sensitivity_vs_frequency2.pdf", # optional "figsize": (10,6), "fontsize": 15, @@ -83,7 +82,7 @@ def test_SensitivityCurveProcessor(self): "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "y_limits": [10e-3, 10], "density_range": [1e12,1e19], - "exposure_range": [1e-11, 1e4], + #"exposure_range": [1e-11, 1e4], "frequency_range": [1e6, 20e9], #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], @@ -107,9 +106,9 @@ def test_SensitivityCurveProcessor(self): "goals_y_rel_position": 0.4, "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict2) + #sens_curve.Run() sens_config_dict = { @@ -181,9 +180,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -201,7 +200,7 @@ def test_SensitivityCurveProcessor(self): "y_limits": [2e-2, 4], "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], - "main_curve_upper_label": r"Molecular, best-case-scenario", + "main_curve_upper_label": r"Molecular, best-case scenario", "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": False, "sigmae_theta_r": 0.159, @@ -215,6 +214,41 @@ def test_SensitivityCurveProcessor(self): #sens_curve.Configure(sens_config_dict) #sens_curve.Run() + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_best_case_curve_comparison.pdf", + # optional + "figsize": (6.7,6), + "legend_location": "upper left", + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [3e-2, 0.5], + "density_range": [1e14, 7e17],#[1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r"Atomic, reaching target", #$\Delta B_{r, \phi}=0$, rate 'boosted' $\times 2$ + "goals": {"Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg"], + "comparison_curve_label": [r"Molecular, same conditions"], + "comparison_label_y_position": [2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], + "sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e14, + "goals_y_rel_position": 1.1, + "plot_key_parameters": True + } + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() + def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 0a76fa694e30a258c3a2bc3d9180d9cfa0878d93 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 7 Jul 2023 17:08:32 -0700 Subject: [PATCH 079/262] changes to frequency scaling plot --- mermithid/misc/SensitivityCavityFormulas.py | 8 +- .../CavitySensitivityCurveProcessor.py | 73 +++++++++++++------ tests/Sensitivity_test.py | 11 ++- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 0728ad30..221c9cfd 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -1,7 +1,7 @@ ''' Class calculating neutrino mass sensitivities based on analytic formulas from CDR. -Author: R. Reimann, C. Claessens, T. Weiss -Date:11/17/2020 +Author: R. Reimann, C. Claessens, T. Weiss, W. Van De Pontseele +Date:06/07/2023 The statistical method and formulars are described in CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. @@ -630,7 +630,7 @@ def syst_magnetic_field(self): return sigma, delta B = self.MagneticField.nominal_field - if self.MagneticField.useinhomogenarity: + if self.MagneticField.useinhomogeneity: frac_uncertainty = self.MagneticField.fraction_uncertainty_on_field_broadening sigma_meanB = self.MagneticField.sigma_meanb sigmaE_meanB = self.BToKeErr(sigma_meanB*B, B) @@ -639,6 +639,8 @@ def syst_magnetic_field(self): sigmaE_phi = self.MagneticField.sigmae_theta sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r**2 + sigmaE_theta**2 + sigmaE_phi**2) return sigma, frac_uncertainty*sigma + else: + return 0, 0 """ BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index f2ce5b87..e90a96ac 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -1,8 +1,8 @@ ''' Calculate sensitivity curve and plot vs. pressure function. -Author: R. Reimann, C. Claessens, T. Weiss -Date:11/17/2020 +Author: C. Claessens, T. Weiss +Date:06/07/2023 More description ''' @@ -70,30 +70,38 @@ def InternalConfigure(self, params): self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) self.sigmae_theta_r = reader.read_param(params, 'sigmae_theta_r', 0.159) #eV - + self.configure_sigma_theta_r = reader.read_param(params, "configure_sigma_theta_r", False) # main plot configurations self.figsize = reader.read_param(params, 'figsize', (6,6)) self.legend_location = reader.read_param(params, 'legend_location', 'upper left') self.fontsize = reader.read_param(params, 'fontsize', 12) - self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) - self.year_range = reader.read_param(params, "years_range", [0.1, 10]) - self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) - self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) + self.density_axis = reader.read_param(params, "density_axis", True) self.frequency_axis = reader.read_param(params, "frequency_axis", False) - self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) + self.exposure_axis = reader.read_param(params, "exposure_axis", False) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + self.magnetic_field_axis = reader.read_param(params, 'magnetic_field_axis', False) + + self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) + self.year_range = reader.read_param(params, "years_range", [0.1, 10]) + self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) + self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) + + self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) + self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) + self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) + self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) + self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.comparison_label_y_position = reader.read_param(params, 'comparison_label_y_position', 0.044) self.comparison_label_x_position = reader.read_param(params, 'comparison_label_x_position', 5e16) if self.comparison_label_x_position == None: self.comparison_label_x_position = reader.read_param(params, 'label_x_position', 5e16) - self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) - self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) - self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) self.add_1year_1cav_point_to_last_ref = reader.read_param(params, "add_1year_1cav_point_to_last_ref", False) @@ -166,10 +174,10 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 500)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 1000)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year - self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 10)*Hz + self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz return True @@ -180,12 +188,15 @@ def InternalRun(self): self.create_plot() # optionally add Phase II curve and point to exposure plot - if self.density_axis == False and self.add_PhaseII: + if self.exposure_axis and self.add_PhaseII: self.add_Phase_II_exposure_sens_line(self.sens_PhaseII) # add second and third x axis for track lengths - if self.track_length_axis: + if self.density_axis and self.track_length_axis: self.add_track_length_axis() + + if self.frequency_axis and self.magnetic_field_axis: + self.add_magnetic_field_axis() for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) @@ -198,7 +209,7 @@ def InternalRun(self): self.sens_main.Experiment.number_density = rho_opt # if B is list plot line for each B - if isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray): + if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): N = len(self.sigmae_theta_r) for a, color in self.range(0, N): #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) @@ -218,8 +229,9 @@ def InternalRun(self): #self.sens_main.MagneticField.usefixedvalue = True #self.sens_main.MagneticField.default_systematic_smearing = sig #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig - self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV - self.sens_main.MagneticField.sigmae_theta = 0 * eV + if self.configure_sigma_theta_r: + self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV + self.sens_main.MagneticField.sigmae_theta = 0 * eV self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') @@ -240,10 +252,10 @@ def InternalRun(self): #self.sens_main.MagneticField.default_systematic_smearing = sig #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig - if isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray): + if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[0] * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - else: + elif self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV @@ -427,6 +439,15 @@ def add_track_length_axis(self): else: logger.warning("No track length axis added since neither atomic nor molecular was requested") self.fig.tight_layout() + + def add_magnetic_field_axis(self): + ax3 = self.ax.twiny() + ax3.set_xscale("log") + ax3.set_xlabel("Magnetic field strength (T)") + + gamma = self.sens_main.T_endpoint/(me*c0**2) + 1 + ax3.set_xlim(self.frequencies[0]/(e/(2*np.pi*me)/gamma)/T, self.frequencies[-1]/(e/(2*np.pi*me)/gamma)/T) + def add_comparison_curve(self, label, color='k'): @@ -665,8 +686,11 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): rho_limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] limits.append(np.min(rho_limits)) - opt_rho.append(self.rhos[np.argmin(rho_limits)]*m**3) - sens.Experiment.number_density = self.rhos[np.argmin(rho_limits)] + this_optimum_rho = self.rhos[np.argmin(rho_limits)] + if this_optimum_rho == self.rhos[0] or this_optimum_rho == self.rhos[-1]: + raise ValueError("Cannot optimize density. Ideal value {:2e} at edge of range".format(this_optimum_rho*m**3)) + opt_rho.append(this_optimum_rho*m**3) + sens.Experiment.number_density = this_optimum_rho # other quantities resolutions.append(sens.syst_frequency_extraction()[0]/meV) @@ -707,6 +731,11 @@ def range(self, start, stop): def save(self, savepath, **kwargs): if self.density_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + elif self.frequency_axis: + if self.magnetic_field_axis: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) + else: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 )) else: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.89,0.97)) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index cbc24322..33e8bac3 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -69,24 +69,23 @@ def test_SensitivityCurveProcessor(self): #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_frequency.pdf", # optional - "figsize": (10,6), + "figsize": (9,6), "fontsize": 15, - "legend_location": "upper right", + "legend_location": "upper left", "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, "density_axis": False, "frequency_axis": True, + "magnetic_field_axis": True, "cavity": True, "add_PhaseII": False, "add_1year_1cav_point_to_last_ref": False, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "y_limits": [10e-3, 10], - "density_range": [1e12,1e19], - "exposure_range": [1e-11, 1e4], - "frequency_range": [1e6, 20e9], + "frequency_range": [1e7, 20e9], #"efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], + "density_range": [1e7, 1e20], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, From 368521fa0d251131f46611815684887bc265167c Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 19 Jul 2023 14:05:31 -0700 Subject: [PATCH 080/262] SNR calculations --- mermithid/misc/SensitivityCavityFormulas.py | 29 +++++++++++-- .../CavitySensitivityCurveProcessor.py | 39 +++++++++++++---- tests/Sensitivity_test.py | 42 ++++++++++++++++--- 3 files changed, 93 insertions(+), 17 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 0728ad30..74ffcede 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -29,7 +29,7 @@ tritium_mass_molecular = 6.032099 * amu *c0**2 tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 #[Inelastic from Aseev (2000) for T2] + [Elastic from Liu (1987) for H2, extrapolated by Elise to 18.6keV] -tritium_endpoint_molecular = 18573.24*eV +tritium_endpoint_molecular = 18574.01*eV last_1ev_fraction_molecular = 1.67364e-13/eV**3 ground_state_width = 0.436 * eV @@ -426,7 +426,8 @@ def get_systematics(self): return np.array(labels), np.array(sigmas), np.array(deltas) def print_statistics(self): - print("Statistical", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() @@ -438,6 +439,12 @@ def print_systematics(self): sigma_squared += sigma**2 sigma_total = np.sqrt(sigma_squared) print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) + try: + print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") + except AttributeError: + pass + print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") + print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from @@ -449,7 +456,7 @@ def syst_doppler_broadening(self): delta = self.DopplerBroadening.Default_Systematic_Uncertainty return sigma, delta - # termal doppler broardening + # thermal doppler broardening gasTemp = self.DopplerBroadening.gas_temperature mass_T = self.T_mass endpoint = self.T_endpoint @@ -509,11 +516,25 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) tau_snr = kB*tn_system_fft/P_signal_received - + # end of Wouter's calculation return tau_snr + def print_SNRs(self, rho_opt): + tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) + logger.info("tau_SNR: {}s".format(tau_snr/s)) + eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) + SNR_1eV = 1/eV_bandwidth/2./tau_snr + track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) + SNR_track_duration = track_duration/2./tau_snr + SNR_1ms = 0.001*s/2./tau_snr + logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) + logger.info("Track duration: {}ms".format(track_duration/ms)) + logger.info("SNR for track duration: {}".format(SNR_track_duration)) + logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + + def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 11d8dd46..5d6ae3ff 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -177,6 +177,29 @@ def InternalConfigure(self, params): def InternalRun(self): + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + for n in self.rhos: + self.sens_main.Experiment.number_density = n + labels, sigmas, deltas = self.sens_main.get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_main.StatSens()) + syst_on_mbeta2.append(self.sens_main.SystSens()) + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.savefig("stat_and_syst_vs_density.pdf") + + fig = plt.figure() + plt.loglog(self.rhos*m**3, sigma_startf/eV) + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") + plt.savefig("resolution_from_CRLB_vs_density.pdf") + + self.create_plot() # optionally add Phase II curve and point to exposure plot @@ -258,6 +281,7 @@ def InternalRun(self): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + self.sens_main.print_SNRs(rho_opt) logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -289,6 +313,7 @@ def InternalRun(self): if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) + self.sens_ref[i].print_SNRs(rho_opt_ref) logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* @@ -299,8 +324,8 @@ def InternalRun(self): self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) - self.sens_ref[i].print_statistics() - self.sens_ref[i].print_systematics() + self.sens_ref[i].print_statistics() + self.sens_ref[i].print_systematics() # save plot self.save(self.plot_path) @@ -320,13 +345,13 @@ def create_plot(self): ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) if self.atomic_axis and self.molecular_axis: - axis_label = r"(Atomic / molecular) number density $\rho\, \, (\mathrm{m}^{-3})$" + axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" elif self.atomic_axis: - axis_label = r"(Atomic) number density $\rho\, \, (\mathrm{m}^{-3})$" + axis_label = r"(Atomic) number density $n\, \, (\mathrm{m}^{-3})$" elif self.molecular_axis: - axis_label = r"(Molecular) number density $\rho\, \, (\mathrm{m}^{-3})$" + axis_label = r"(Molecular) number density $n\, \, (\mathrm{m}^{-3})$" else: - axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" + axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) @@ -372,7 +397,7 @@ def create_plot(self): ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) ax.set_xscale("log") ax.set_yscale("log") - axis_label = r"Number density $\rho\, \, (\mathrm{m}^{-3})$" + axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" ax.set_xlabel(axis_label) self.kp_ax[0].set_ylabel('Resolution (meV)') diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 198519f7..2a034e0c 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -180,9 +180,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() sens_config_dict = { @@ -217,7 +217,6 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_T2_best_case_curve_comparison.pdf", # optional "figsize": (6.7,6), @@ -230,25 +229,56 @@ def test_SensitivityCurveProcessor(self): "y_limits": [3e-2, 0.5], "density_range": [1e14, 7e17],#[1e12,3e18], "efficiency_range": [0.0001, 1], - "main_curve_upper_label": r"Atomic, reaching target", #$\Delta B_{r, \phi}=0$, rate 'boosted' $\times 2$ + "main_curve_upper_label": r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", "goals": {"Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg"], "comparison_curve_label": [r"Molecular, same conditions"], "comparison_label_y_position": [2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], - "sigmae_theta_r": 0.159, + "sigmae_theta_r": 0.0, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e14, + "goals_y_rel_position": 1.1, + "plot_key_parameters": True + } + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() + + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_no-DeltaB-r-phi-t.pdf", + # optional + "figsize": (7.5,5), + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [3e-2, 0.5], #[2e-2, 4], + "density_range": [1e14, 7e17], #[1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r" ", #r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", + "goals": {"Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "sigmae_theta_r": 0.0, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, "goals_x_position": 1.2e14, "goals_y_rel_position": 1.1, + #"legend_location": "upper left", "plot_key_parameters": True } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) #sens_curve.Run() + def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation From 6d5d7b4c9cb547dc4d0303b69ebe9936c9cc08be Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 19 Jul 2023 23:05:26 -0700 Subject: [PATCH 081/262] added more noise and snr prints --- mermithid/misc/SensitivityCavityFormulas.py | 11 +++++++++-- .../CavitySensitivityCurveProcessor.py | 2 +- tests/stat_and_syst_vs_density.pdf | Bin 0 -> 17579 bytes 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 tests/stat_and_syst_vs_density.pdf diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index d6be1135..faf4ebdc 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -262,7 +262,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=10000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, @@ -498,6 +498,7 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # Noise power for bandwidth set by density/track length fft_bandwidth = 3/time_window #(delta f) is the frequency bandwidth of interest. We have a main carrier and 2 axial side bands, so 3*(FFT bin width) + self.fft_bandwidth = fft_bandwidth tn_fft = Pn_dut_entrance(self.FrequencyExtraction.cavity_temperature, self.FrequencyExtraction.amplifier_temperature, att_line_db_freq,att_cir_db_freq, @@ -508,14 +509,16 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # Noise temperature of amplifier tn_amplifier = endpoint_frequency*hbar*2*np.pi/kB/self.FrequencyExtraction.quantum_amp_efficiency tn_system_fft = tn_amplifier+tn_fft - self.noise_power = tn_system_fft + self.noise_temp = tn_system_fft # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # logger.info("Power: {}".format(Pe/W)) Pe = self.signal_power * sideband_power_fraction P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) + self.received_power = P_signal_received tau_snr = kB*tn_system_fft/P_signal_received + self.noise_energy = kB*tn_system_fft # end of Wouter's calculation return tau_snr @@ -530,9 +533,13 @@ def print_SNRs(self, rho_opt): SNR_track_duration = track_duration/2./tau_snr SNR_1ms = 0.001*s/2./tau_snr logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) + logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) logger.info("Track duration: {}ms".format(track_duration/ms)) + logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) logger.info("SNR for track duration: {}".format(SNR_track_duration)) logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + logger.info("Received power: {}W".format(self.received_power/W)) + logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) def syst_frequency_extraction(self): diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 1c0a329e..a6332ae3 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -722,7 +722,7 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) - noise_power.append(sens.noise_power/K) + noise_power.append(sens.noise_temp/K) self.ax2.plot(self.frequencies/Hz, limits, **kwargs) diff --git a/tests/stat_and_syst_vs_density.pdf b/tests/stat_and_syst_vs_density.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c7c725750f048afdca09b469cd4cc2eec352f0f3 GIT binary patch literal 17579 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 literal 0 HcmV?d00001 From f3e35bc56da1ab039a647fd65f2ce5f6f9fed1e9 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Fri, 21 Jul 2023 09:12:56 -0700 Subject: [PATCH 082/262] Check cavity Q compared with Hamish's formula --- mermithid/misc/SensitivityCavityFormulas.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index d6be1135..c12cc197 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -274,6 +274,9 @@ def CavityLoadedQ(self): # Using Wouter's calculation: # Total required bandwidth is the sum of the endpoint region and the axial frequency. # I will assume the bandwidth is dominated by the sidebands and not by the energy ROI + + #self.loaded_q =1/(0.22800*((90-self.FrequencyExtraction.minimum_angle_in_bandwidth)*np.pi/180)**2+2**2*0.01076**2/(4*0.22800)) + endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint, From a22be8063db83aeeec41a1ce6e015dea8f04a7ed Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 21 Jul 2023 10:18:19 -0700 Subject: [PATCH 083/262] "finding factor 22" --- mermithid/misc/SensitivityCavityFormulas.py | 2 +- .../CavitySensitivityCurveProcessor.py | 2 +- tests/Sensitivity_test.py | 22 +++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index faf4ebdc..4bd7ecfb 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -262,7 +262,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=10000), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index a6332ae3..ea9923b2 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -255,7 +255,7 @@ def InternalRun(self): if self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, color='darkred', label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') # add line for comparison using second config diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index e58177e5..a41168a6 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -149,13 +149,13 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), "track_length_axis": True, - "molecular_axis": True, + "molecular_axis": False, "atomic_axis": True, "density_axis": True, "cavity": True, @@ -163,16 +163,20 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular, conservative", #r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", r"Atomic, reaching target"], #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "main_curve_upper_label": r"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": [#"Molecular, reaching target", + "Atomic, alternative scenario 1"], + #"Atomic, reaching target"], + # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], - "sigmae_theta_r": 0.159, + #"sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, From 20309d662913aadc26d7e14485c49eb853e9d119 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 26 Sep 2023 14:58:40 -0700 Subject: [PATCH 084/262] added cca test scenario --- mermithid/misc/SensitivityCavityFormulas.py | 6 +++ .../CavitySensitivityCurveProcessor.py | 7 ++- tests/Sensitivity_test.py | 48 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 4bd7ecfb..edaa3145 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -225,6 +225,7 @@ def __init__(self, config_path): self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) self.CRLB_constant = 12 + #self.CRLB_constant = 90 self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -243,6 +244,7 @@ def CavityVolume(self): logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Radius: {} cm".format(round(self.cavity_radius/cm, 3))) + logger.info("Length: {} cm".format(round(2*self.cavity_radius*self.Experiment.L_over_D/cm, 3))) logger.info("Total volume {} m^3".format(round(self.total_volume/m**3))) return self.total_volume @@ -278,12 +280,14 @@ def CavityLoadedQ(self): required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, self.T_endpoint, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + self.required_bw_axialfrequency = required_bw_axialfrequency required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, self.Experiment.L_over_D, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting + self.required_bw = required_bw # Cavity coupling self.loaded_q = endpoint_frequency/required_bw # FWHM @@ -540,6 +544,8 @@ def print_SNRs(self, rho_opt): logger.info("SNR for 1 ms: {}".format(SNR_1ms)) logger.info("Received power: {}W".format(self.received_power/W)) logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) + logger.info("Noise temperature: {}K".format(self.noise_temp/K)) + logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) def syst_frequency_extraction(self): diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index ea9923b2..06a15592 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -255,7 +255,7 @@ def InternalRun(self): if self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color='darkred', label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') # add line for comparison using second config @@ -287,6 +287,8 @@ def InternalRun(self): self.sens_main.CL90(Experiment={"number_density": rho_opt}) logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) logger.info("Loaded Q: {}".format(self.sens_main.loaded_q)) + logger.info("Axial frequency for minimum detectable angle: {} MHz".format(self.sens_main.required_bw_axialfrequency/MHz)) + logger.info("Total bandwidth: {} MHz".format(self.sens_main.required_bw/MHz)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) @@ -437,8 +439,9 @@ def create_plot(self): def add_track_length_axis(self): - N_ref = len(self.sens_ref) + if self.atomic_axis: + N_ref = len(self.sens_ref) ax2 = self.ax.twiny() ax2.set_xscale("log") ax2.set_xlabel("(Atomic) track length (s)") diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index a41168a6..6ebd9afb 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -163,7 +163,7 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", + "main_curve_upper_label": r"Atomic, reaching target", #r"Molecular"+"\n"+"Reaching target", "comparison_curve_label": [#"Molecular, reaching target", "Atomic, alternative scenario 1"], #"Atomic, reaching target"], @@ -183,9 +183,9 @@ def test_SensitivityCurveProcessor(self): "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + #sens_curve.Configure(sens_config_dict) + #sens_curve.Run() sens_config_dict = { @@ -280,6 +280,46 @@ def test_SensitivityCurveProcessor(self): #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) #sens_curve.Run() + + sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./cca_sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 20], + "density_range": [1e13,1e21], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"CCA", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": [#"Molecular, reaching target", + "Atomic, alternative scenario 1"], + #"Atomic, reaching target"], + # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": True + } + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() def test_SensitivityProcessor(self): From cf195d4fa1edb7342cdd48819513b3c86b4363e5 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 6 Oct 2023 11:29:13 -0700 Subject: [PATCH 085/262] moved cavity sensitivity to Cavity_sensitivity_analysis.py in test_analysis and only left 2 examples in the Sensitivity_test.py script --- test_analysis/Cavity_Sensitivity_analysis.py | 333 +++++++++++++++++++ tests/Sensitivity_test.py | 237 +------------ 2 files changed, 345 insertions(+), 225 deletions(-) create mode 100644 test_analysis/Cavity_Sensitivity_analysis.py diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py new file mode 100644 index 00000000..d632031d --- /dev/null +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -0,0 +1,333 @@ +""" +Script to make Sensitivty plots for cavity experiments +Author: C. Claessens, T. Weiss +Date: October 6, 2023 +""" + + +from morpho.utilities import morphologging, parser +logger = morphologging.getLogger(__name__) + +from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor + +import numpy as np + +# Configuration for Sensitivity vs. exposure plot +# Phase III and IV at 325 MHz +# Phase II point and curve for comparison + +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_exposure_curve.pdf", + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "cavity": True, + "add_PhaseII": True, + "add_1year_1cav_point_to_last_ref": True, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "y_limits": [10e-3, 500], + "density_range": [1e12,1e19], + "exposure_range": [1e-11, 1e4], + "main_curve_upper_label": r"Molecular, conservative", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_colors": ["blue", "darkred", "red"], + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4 + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + +# Configuration for Sensitivity vs. frequency plot +sens_config_dict2 = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_frequency2.pdf", + # optional + "figsize": (9,6), + "fontsize": 15, + "legend_location": "upper left", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "frequency_axis": True, + "magnetic_field_axis": True, + "cavity": True, + "add_PhaseII": False, + "add_1year_1cav_point_to_last_ref": False, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "y_limits": [10e-3, 10], + "frequency_range": [1e7, 20e9], + #"efficiency_range": [0.0001, 1], + "density_range": [1e7, 1e20], + "main_curve_upper_label": r"Molecular, conservative", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_colors": ["blue", "darkred", "red"], + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 1e8, #1.5e15, #0.02, #1e14, + "goals_x_position": 1e9, #2e12 #0.0002 + "goals_y_rel_position": 0.4, + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict2) +#sens_curve.Run() + +# Configuration for Sensitivity vs. density plot for different B sigmas +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_curve_with_sigmaBcorrs.pdf", + # optional + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,1e19], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", + "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", + "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, + #"B_inhom_uncertainty": 0.01, + "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction + "comparison_label_y_position": [0.044], + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 1.5e15, #0.02, #1e14, + "goals_x_position": 2e12, #0.0002 + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + +# Configuration for Sensitivity vs. density plot +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"Atomic, reaching target", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": [#"Molecular, reaching target", + "Atomic, alternative scenario 1"], + #"Atomic, reaching target"], + # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": True + } +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + + +# Configuration for Sensitivity vs. density plot for best possible molecular scenario +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_best_case_curve.pdf", + # optional + "figsize": (6.7,6), + "track_length_axis": False, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r"Molecular, best-case scenario", + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e12, + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + +# Configuration for Sensitivity vs. densty plot for molecular best case and Phase III atomic scenarios +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_best_case_curve_comparison.pdf", + # optional + "figsize": (6.7,6), + "legend_location": "upper left", + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [3e-2, 0.5], + "density_range": [1e14, 7e17],#[1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", + "goals": {"Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg"], + "comparison_curve_label": [r"Molecular, same conditions"], + "comparison_label_y_position": [2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], + "sigmae_theta_r": 0.0, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e14, + "goals_y_rel_position": 1.1, + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + +# Configuration for Sensitivity vs. density plot with 0 positional field uncertainty +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "plot_path": "./sensitivity_vs_density_T2_no-DeltaB-r-phi-t.pdf", + # optional + "figsize": (7.5,5), + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [3e-2, 0.5], #[2e-2, 4], + "density_range": [1e14, 7e17], #[1e12,3e18], + "efficiency_range": [0.0001, 1], + "main_curve_upper_label": r" ", #r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", + "goals": {"Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "sigmae_theta_r": 0.0, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, + "goals_x_position": 1.2e14, + "goals_y_rel_position": 1.1, + #"legend_location": "upper left", + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + +# Configuration for CCA Sensitivity vs. density plot +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./cca_sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 20], + "density_range": [1e13,1e21], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"CCA", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": [#"Molecular, reaching target", + "Atomic, alternative scenario 1"], + #"Atomic, reaching target"], + # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": True + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + + + + + + + + diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 6ebd9afb..676fc11b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -15,11 +15,14 @@ class SensitivityTest(unittest.TestCase): def test_SensitivityCurveProcessor(self): from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor + + # Configuration for Sensitivity vs. exposure plot + # Phase III and IV at 325 MHz + # Phase II point and curve for comparison sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional "figsize": (10,6), @@ -36,8 +39,6 @@ def test_SensitivityCurveProcessor(self): "y_limits": [10e-3, 500], "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], - #"efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, @@ -47,7 +48,6 @@ def test_SensitivityCurveProcessor(self): "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], "comparison_curve_colors": ["blue", "darkred", "red"], - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction @@ -62,91 +62,8 @@ def test_SensitivityCurveProcessor(self): #sens_curve.Run() - sens_config_dict2 = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_frequency2.pdf", - # optional - "figsize": (9,6), - "fontsize": 15, - "legend_location": "upper left", - "track_length_axis": False, - "molecular_axis": False, - "atomic_axis": False, - "density_axis": False, - "frequency_axis": True, - "magnetic_field_axis": True, - "cavity": True, - "add_PhaseII": False, - "add_1year_1cav_point_to_last_ref": False, - "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "y_limits": [10e-3, 10], - "frequency_range": [1e7, 20e9], - #"efficiency_range": [0.0001, 1], - "density_range": [1e7, 1e20], - "main_curve_upper_label": r"Molecular, conservative", - "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, - "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_colors": ["blue", "darkred", "red"], - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, - #"B_inhom_uncertainty": 0.01, - "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 1e8, #1.5e15, #0.02, #1e14, - "goals_x_position": 1e9, #2e12 #0.0002 - "goals_y_rel_position": 0.4, - "plot_key_parameters": True - } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict2) - #sens_curve.Run() - - - sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_density_curve_with_sigmaBcorrs.pdf", - # optional - "track_length_axis": True, - "molecular_axis": True, - "atomic_axis": True, - "density_axis": True, - "cavity": True, - "y_limits": [2e-2, 4], - "density_range": [1e12,1e19], - "efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Molecular"+"\n"+"2 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", - "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", - "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], - "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, - "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, - #"B_inhom_uncertainty": 0.01, - "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction - "comparison_label_y_position": [0.044], - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 1.5e15, #0.02, #1e14, - "goals_x_position": 2e12, #0.0002 - "plot_key_parameters": True - } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() - - + # Configuration for Sensitivity vs. density plot + # Find optimum density for different scenarios sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", @@ -188,148 +105,16 @@ def test_SensitivityCurveProcessor(self): #sens_curve.Run() - sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_density_T2_best_case_curve.pdf", - # optional - "figsize": (6.7,6), - "track_length_axis": False, - "molecular_axis": True, - "atomic_axis": False, - "density_axis": True, - "cavity": True, - "y_limits": [2e-2, 4], - "density_range": [1e12,3e18], - "efficiency_range": [0.0001, 1], - "main_curve_upper_label": r"Molecular, best-case scenario", - "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, - "comparison_curve": False, - "sigmae_theta_r": 0.159, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, - "goals_x_position": 1.2e12, - "plot_key_parameters": True - } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() - - sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_density_T2_best_case_curve_comparison.pdf", - # optional - "figsize": (6.7,6), - "legend_location": "upper left", - "track_length_axis": True, - "molecular_axis": True, - "atomic_axis": True, - "density_axis": True, - "cavity": True, - "y_limits": [3e-2, 0.5], - "density_range": [1e14, 7e17],#[1e12,3e18], - "efficiency_range": [0.0001, 1], - "main_curve_upper_label": r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", - "goals": {"Phase IV (0.04 eV)": 0.04}, - "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg"], - "comparison_curve_label": [r"Molecular, same conditions"], - "comparison_label_y_position": [2, 0.105, 0.046], - "comparison_label_x_position": [4.5e15, 7e14, 7e14], - "sigmae_theta_r": 0.0, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, - "goals_x_position": 1.2e14, - "goals_y_rel_position": 1.1, - "plot_key_parameters": True - } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() - - sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - "plot_path": "./sensitivity_vs_density_T2_no-DeltaB-r-phi-t.pdf", - # optional - "figsize": (7.5,5), - "track_length_axis": False, - "molecular_axis": False, - "atomic_axis": True, - "density_axis": True, - "cavity": True, - "y_limits": [3e-2, 0.5], #[2e-2, 4], - "density_range": [1e14, 7e17], #[1e12,3e18], - "efficiency_range": [0.0001, 1], - "main_curve_upper_label": r" ", #r"Atomic, $\Delta B_{r, \phi, t}=0$, rate 'boosted' $\times 2$", - "goals": {"Phase IV (0.04 eV)": 0.04}, - "comparison_curve": False, - "sigmae_theta_r": 0.0, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, - "goals_x_position": 1.2e14, - "goals_y_rel_position": 1.1, - #"legend_location": "upper left", - "plot_key_parameters": True - } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() - - sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "plot_path": "./cca_sensitivity_vs_density_curve.pdf", - # optional - "figsize": (7.0,6), - "track_length_axis": True, - "molecular_axis": True, - "atomic_axis": False, - "density_axis": True, - "cavity": True, - "y_limits": [2e-2, 20], - "density_range": [1e13,1e21], - "efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"CCA", #r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": [#"Molecular, reaching target", - "Atomic, alternative scenario 1"], - #"Atomic, reaching target"], - # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, - "comparison_curve": False, - "comparison_curve_colors": ["red"], - "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], - "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], - #"sigmae_theta_r": 0.159, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 1.2e12, #0.0002 - "plot_key_parameters": True - } - sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - sens_curve.Configure(sens_config_dict) - sens_curve.Run() - def test_SensitivityProcessor(self): from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation - + + # Calculates sensitivity for given configuration (no parameter optimization or scanning) + # Uses the non-cavity specific sensitivity calculations sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" } # sens = AnalyticSensitivityEstimation("sensitivity_processor") # sens.Configure(sens_config_dict) @@ -343,7 +128,9 @@ def test_SensitivityProcessor(self): def test_ConstantSensitivityCurvesProcessor(self): from mermithid.processors.Sensitivity import ConstantSensitivityParameterPlots - + + # Makes plots of key parameters vs. configured inputs with fixed target sensitivity + # Uses the non-cavity specific sensitivity calculations sens_config_dict = { # required From 5abc55e31b3bfd2f6298dd65ccf3469ffd682277 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 6 Oct 2023 11:37:55 -0700 Subject: [PATCH 086/262] some path cleaning --- test_analysis/Cavity_Sensitivity_analysis.py | 5 ----- tests/Sensitivity_test.py | 2 -- 2 files changed, 7 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index d632031d..ada035ec 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -62,7 +62,6 @@ sens_config_dict2 = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_frequency2.pdf", # optional "figsize": (9,6), @@ -110,7 +109,6 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve_with_sigmaBcorrs.pdf", # optional "track_length_axis": True, @@ -148,7 +146,6 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), @@ -190,7 +187,6 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_best_case.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_T2_best_case_curve.pdf", # optional "figsize": (6.7,6), @@ -286,7 +282,6 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./cca_sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 676fc11b..c0ff370b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -67,7 +67,6 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", - #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), @@ -135,7 +134,6 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", - #"config_file_path": "//host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", "sensitivity_target": [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] } #sens = ConstantSensitivityParameterPlots("sensitivity_processor") From fecdba89bb1efafb6aa7797dcb000e8207bc6ed6 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 6 Oct 2023 13:58:28 -0700 Subject: [PATCH 087/262] adding pdf to gitignore and cca sensitivity script --- .gitignore | 1 + test_analysis/CCA_Sensitivity.py | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 test_analysis/CCA_Sensitivity.py diff --git a/.gitignore b/.gitignore index 80b79d73..0113c9ca 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,4 @@ ENV/ .mypy_cache/ *.root *.png +*.pdf diff --git a/test_analysis/CCA_Sensitivity.py b/test_analysis/CCA_Sensitivity.py new file mode 100644 index 00000000..9fe89eb9 --- /dev/null +++ b/test_analysis/CCA_Sensitivity.py @@ -0,0 +1,63 @@ +""" +Script to make Sensitivty plots for cavity experiments +Author: C. Claessens, T. Weiss +Date: October 6, 2023 +""" + + +from morpho.utilities import morphologging, parser +logger = morphologging.getLogger(__name__) + +from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor + +import numpy as np + +# Configuration for CCA Sensitivity vs. density plot +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./cca_sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 20], + "density_range": [1e13,1e21], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"CCA", #r"Molecular"+"\n"+"Reaching target", + "comparison_curve_label": [#"Molecular, reaching target", + "Atomic, alternative scenario 1"], + #"Atomic, reaching target"], + # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": True + } +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + + + + + + + + + From 808c32c75abd5f05f856dc0a0bae07756584ac5f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 8 Oct 2023 10:06:55 -0700 Subject: [PATCH 088/262] added DS_Store to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0113c9ca..4ce8dcca 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ ENV/ *.root *.png *.pdf +*.DS_Store From 970c82caf699da5521baf01b4261334be76d229f Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 8 Oct 2023 12:41:00 -0700 Subject: [PATCH 089/262] Change configuration in Sensitivity_test.py to recreate proposal density plot --- tests/Sensitivity_test.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index c0ff370b..8f3f6c7c 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -66,12 +66,12 @@ def test_SensitivityCurveProcessor(self): # Find optimum density for different scenarios sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), "track_length_axis": True, - "molecular_axis": False, + "molecular_axis": True, "atomic_axis": True, "density_axis": True, "cavity": True, @@ -79,17 +79,17 @@ def test_SensitivityCurveProcessor(self): "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Atomic, reaching target", #r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": [#"Molecular, reaching target", - "Atomic, alternative scenario 1"], - #"Atomic, reaching target"], + "main_curve_upper_label": r"Molecular, conservative", + "comparison_curve_label": ["Molecular, reaching target", + "Atomic, conservative", + "Atomic, reaching target"], # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_curve_colors": ["red"], - "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + #"comparison_curve_colors": ["red"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, @@ -97,11 +97,11 @@ def test_SensitivityCurveProcessor(self): "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e12, #0.0002 - "plot_key_parameters": True + "plot_key_parameters": False } - #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") - #sens_curve.Configure(sens_config_dict) - #sens_curve.Run() + sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") + sens_curve.Configure(sens_config_dict) + sens_curve.Run() From 710da2aed44961e07ea34e4cfed23b836ae82f3b Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 9 Oct 2023 12:15:05 -0700 Subject: [PATCH 090/262] checked all plots in Cavity_Sensitivity_analysis.py still work and made stat_sys_vs_density a key parameter plot --- .../CavitySensitivityCurveProcessor.py | 83 ++++++++++--------- test_analysis/Cavity_Sensitivity_analysis.py | 26 +++--- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 06a15592..fb3ce3a8 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -106,7 +106,7 @@ def InternalConfigure(self, params): self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) self.add_1year_1cav_point_to_last_ref = reader.read_param(params, "add_1year_1cav_point_to_last_ref", False) - # other plot + # key parameter plots self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) if self.density_axis: @@ -122,12 +122,12 @@ def InternalConfigure(self, params): # goals self.goals = reader.read_param(params, "goals", {}) - - if self.add_PhaseII: - self.sens_PhaseII = CavitySensitivity(self.PhaseII_path) # setup sensitivities + if self.add_PhaseII: + self.sens_PhaseII = CavitySensitivity(self.PhaseII_path) + self.cavity = reader.read_param(params, 'cavity', True) if self.cavity: @@ -185,29 +185,40 @@ def InternalConfigure(self, params): def InternalRun(self): - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] - for n in self.rhos: + + + if self.make_key_parameter_plots: + + # First key parameter plot: Stat and Syst vs. density + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + + for n in self.rhos: self.sens_main.Experiment.number_density = n labels, sigmas, deltas = self.sens_main.get_systematics() sigma_startf.append(sigmas[1]) stat_on_mbeta2.append(self.sens_main.StatSens()) syst_on_mbeta2.append(self.sens_main.SystSens()) - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) - fig = plt.figure() - plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') - plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") - plt.legend() - plt.savefig("stat_and_syst_vs_density.pdf") - - fig = plt.figure() - plt.loglog(self.rhos*m**3, sigma_startf/eV) - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") - plt.savefig("resolution_from_CRLB_vs_density.pdf") - - + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig("stat_and_syst_vs_density.pdf") + + fig = plt.figure() + plt.loglog(self.rhos*m**3, sigma_startf/eV) + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") + plt.tight_layout() + plt.savefig("resolution_from_CRLB_vs_density.pdf") + + + # create main plot self.create_plot() # optionally add Phase II curve and point to exposure plot @@ -218,14 +229,16 @@ def InternalRun(self): if self.density_axis and self.track_length_axis: self.add_track_length_axis() + # add magnetic field axis if frequency axis if self.frequency_axis and self.magnetic_field_axis: self.add_magnetic_field_axis() + # add goals for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) self.add_goal(value, key) - # first optimize density + # optimize density limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] opt = np.argmin(limit) rho_opt = self.rhos[opt] @@ -274,7 +287,8 @@ def InternalRun(self): #self.sens_main.MagneticField.usefixedvalue = True #self.sens_main.MagneticField.default_systematic_smearing = sig #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig - + + # if the magnetic field uncertainties were configured above, set them back to the first value in the list if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[0] * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV @@ -282,7 +296,7 @@ def InternalRun(self): self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - logger.info('Main curve (molecular):') + logger.info('Main curve:') # set optimum density back self.sens_main.CL90(Experiment={"number_density": rho_opt}) logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) @@ -309,7 +323,7 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() - # Optimize atomic density + # Optimize comparison curves over density if self.comparison_curve: for i in range(len(self.sens_ref)): limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] @@ -435,9 +449,8 @@ def create_plot(self): self.kp_ax[2].set_ylabel(r'Total and effective (dashed) Volume (m$^3$)') self.kp_ax[3].set_ylabel('Noise temperature (K)') - self.kp_fig.tight_layout() + self.kp_fig.tight_layout() - def add_track_length_axis(self): if self.atomic_axis: @@ -475,9 +488,7 @@ def add_magnetic_field_axis(self): gamma = self.sens_main.T_endpoint/(me*c0**2) + 1 ax3.set_xlim(self.frequencies[0]/(e/(2*np.pi*me)/gamma)/T, self.frequencies[-1]/(e/(2*np.pi*me)/gamma)/T) - - - + def add_comparison_curve(self, label, color='k'): for a in range(len(self.sens_ref)): @@ -589,9 +600,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.28*limit))) sens.print_statistics() sens.print_systematics() - - - + def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] @@ -683,8 +692,7 @@ def get_relative(val, axis): ) print(x_start, y_start) self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*0.9, y_start*1.01],textcoords="axes fraction", fontsize=13) - - + def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): limits = [] resolutions = [] @@ -746,8 +754,7 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): self.kp_ax[2].plot(self.frequencies/Hz, effective_volumes, linestyle="--", **kwargs) self.kp_ax[3].plot(self.frequencies/Hz, noise_power, linestyle="-", **kwargs) return limits - - + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index ada035ec..a226cdcd 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -62,7 +62,7 @@ sens_config_dict2 = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_frequency2.pdf", + "plot_path": "./sensitivity_vs_frequency.pdf", # optional "figsize": (9,6), "fontsize": 15, @@ -99,7 +99,7 @@ "label_x_position": 1e8, #1.5e15, #0.02, #1e14, "goals_x_position": 1e9, #2e12 #0.0002 "goals_y_rel_position": 0.4, - "plot_key_parameters": True + "plot_key_parameters": False } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict2) @@ -108,7 +108,7 @@ # Configuration for Sensitivity vs. density plot for different B sigmas sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", "plot_path": "./sensitivity_vs_density_curve_with_sigmaBcorrs.pdf", # optional "track_length_axis": True, @@ -125,17 +125,18 @@ "comparison_curve_label": [r"Atomic"+"\n"+r"10 $\times$ 3 years"+"\n"+r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$"], "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg"], #"comparison_config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, + "configure_sigma_theta_r": True, "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction "comparison_label_y_position": [0.044], "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 1.5e15, #0.02, #1e14, "goals_x_position": 2e12, #0.0002 - "plot_key_parameters": True + "plot_key_parameters": False } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) @@ -143,9 +144,10 @@ # Configuration for Sensitivity vs. density plot +# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "plot_path": "./sensitivity_vs_density_curve.pdf", # optional "figsize": (7.0,6), @@ -158,7 +160,7 @@ "density_range": [1e12,3e18], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Atomic, reaching target", #r"Molecular"+"\n"+"Reaching target", + "main_curve_upper_label": r"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", "comparison_curve_label": [#"Molecular, reaching target", "Atomic, alternative scenario 1"], #"Atomic, reaching target"], @@ -178,9 +180,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() # Configuration for Sensitivity vs. density plot for best possible molecular scenario @@ -206,7 +208,7 @@ "upper_label_y_position": 0.7, "label_x_position": 4e14, "goals_x_position": 1.2e12, - "plot_key_parameters": True + "plot_key_parameters": False } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) @@ -241,7 +243,7 @@ "label_x_position": 4e14, "goals_x_position": 1.2e14, "goals_y_rel_position": 1.1, - "plot_key_parameters": True + "plot_key_parameters": False } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) From c5ada979c1f5098e1dc72460bace100b0754c8c1 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 9 Oct 2023 20:56:59 -0700 Subject: [PATCH 091/262] some cleaning in test script --- mermithid/misc/SensitivityCavityFormulas.py | 7 ++----- tests/Sensitivity_test.py | 9 --------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 13fc319b..032c2cd8 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -94,10 +94,6 @@ def rad_power(kin_energy, pitch, magnetic_field): Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) return Pe -#def rad_power(kin_energy, pitch, magnetic_field): -# Pe = 2*(e**2*magnetic_field*np.sin(pitch))**2*(gamma(kin_energy)**2-1)/(12*eps0*c0*np.pi*me**2) -# return Pe - def track_length(rho, kin_energy=None, molecular=True): if kin_energy is None: kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic @@ -226,6 +222,7 @@ def __init__(self, config_path): self.CRLB_constant = 12 #self.CRLB_constant = 90 + self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -745,4 +742,4 @@ def syst_plasma_effects(self): delta = self.PlasmaEffects.Default_Systematic_Uncertainty return sigma, delta else: - raise NotImplementedError("Plasma effect sysstematic is not implemented.") + raise NotImplementedError("Plasma effect sysstematic is not implemented.") \ No newline at end of file diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 8f3f6c7c..80df485b 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -77,25 +77,16 @@ def test_SensitivityCurveProcessor(self): "cavity": True, "y_limits": [2e-2, 4], "density_range": [1e12,3e18], - "efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], "main_curve_upper_label": r"Molecular, conservative", "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", "Atomic, reaching target"], - # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, #"comparison_curve_colors": ["red"], "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], - "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], - #"sigmae_theta_r": 0.159, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False } From 93a9e6de587625277891975cf82a93fc21d87ae0 Mon Sep 17 00:00:00 2001 From: jkgaison65 Date: Thu, 12 Oct 2023 14:29:00 -0400 Subject: [PATCH 092/262] implementation of configurable crlb constant from Mermithid workshop --- mermithid/misc/SensitivityCavityFormulas.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 032c2cd8..a8483587 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -222,7 +222,10 @@ def __init__(self, config_path): self.CRLB_constant = 12 #self.CRLB_constant = 90 - + if hasattr(self.FrequencyExtraction, "crlb_constant"): + self.CRLB_constant = self.FrequencyExtraction.crlb_constant + logger.info("Using configured CRLB constant") + self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -742,4 +745,4 @@ def syst_plasma_effects(self): delta = self.PlasmaEffects.Default_Systematic_Uncertainty return sigma, delta else: - raise NotImplementedError("Plasma effect sysstematic is not implemented.") \ No newline at end of file + raise NotImplementedError("Plasma effect sysstematic is not implemented.") From 0bad252f654ff78bb9b8e089745f08ae845e878f Mon Sep 17 00:00:00 2001 From: jkgaison65 Date: Fri, 13 Oct 2023 11:03:50 -0400 Subject: [PATCH 093/262] implemented feature to calculate pitch angle-dependent trapping efficiency --- mermithid/misc/SensitivityCavityFormulas.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 032c2cd8..57379829 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -226,6 +226,7 @@ def __init__(self, config_path): self.CavityRadius() self.CavityVolume() self.EffectiveVolume() + self.PitchDependentTrappingEfficiency() self.CavityPower() # CAVITY @@ -251,11 +252,14 @@ def EffectiveVolume(self): self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency else: # trapping efficiecny is currently configured. replace with box trap calculation - self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.trapping_efficiency - + self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.PitchDependentTrappingEfficiency() self.effective_volume*=self.Experiment.sri_factor return self.effective_volume + def PitchDependentTrappingEfficiency(self): + self.pitch_angle_efficiency = np.cos(self.FrequencyExtraction.minimum_angle_in_bandwidth) + return self.pitch_angle_efficiency + def CavityPower(self): # from Hamish's atomic calculator #Jprime_0 = 3.8317 @@ -742,4 +746,4 @@ def syst_plasma_effects(self): delta = self.PlasmaEffects.Default_Systematic_Uncertainty return sigma, delta else: - raise NotImplementedError("Plasma effect sysstematic is not implemented.") \ No newline at end of file + raise NotImplementedError("Plasma effect sysstematic is not implemented.") From c5b8ac7fe3df863e250f54d0bcfcc31060f67551 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 7 Feb 2024 10:12:01 -0800 Subject: [PATCH 094/262] made it possible to not optimize density. added LFA_Sensitivity script --- mermithid/misc/SensitivityCavityFormulas.py | 2 +- .../CavitySensitivityCurveProcessor.py | 100 ++++++++++------- test_analysis/LFA_Sensitivity.py | 103 ++++++++++++++++++ 3 files changed, 164 insertions(+), 41 deletions(-) create mode 100644 test_analysis/LFA_Sensitivity.py diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 032c2cd8..78144af8 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -242,7 +242,7 @@ def CavityVolume(self): logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Radius: {} cm".format(round(self.cavity_radius/cm, 3))) logger.info("Length: {} cm".format(round(2*self.cavity_radius*self.Experiment.L_over_D/cm, 3))) - logger.info("Total volume {} m^3".format(round(self.total_volume/m**3))) + logger.info("Total volume {} m^3".format((self.total_volume/m**3))) return self.total_volume diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index fb3ce3a8..c8e7cf60 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -13,6 +13,7 @@ import matplotlib import matplotlib.pyplot as plt import numpy as np +from copy import deepcopy # Numericalunits is a package to handle units and some natural constants @@ -66,6 +67,8 @@ def InternalConfigure(self, params): self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) # options + self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) + self.optimize_comparison_density = reader.read_param(params, 'optimize_comparison_density', True) self.comparison_curve = reader.read_param(params, 'comparison_curve', False) self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) @@ -194,11 +197,13 @@ def InternalRun(self): sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] for n in self.rhos: + temp_rho = deepcopy(self.sens_main.Experiment.number_density) self.sens_main.Experiment.number_density = n labels, sigmas, deltas = self.sens_main.get_systematics() sigma_startf.append(sigmas[1]) stat_on_mbeta2.append(self.sens_main.StatSens()) syst_on_mbeta2.append(self.sens_main.SystSens()) + self.sens_main.Experiment.number_density = temp_rho sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) fig = plt.figure() @@ -239,10 +244,11 @@ def InternalRun(self): self.add_goal(value, key) # optimize density - limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt = np.argmin(limit) - rho_opt = self.rhos[opt] - self.sens_main.Experiment.number_density = rho_opt + if self.optimize_main_density: + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + self.sens_main.Experiment.number_density = rho_opt # if B is list plot line for each B if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): @@ -271,22 +277,10 @@ def InternalRun(self): self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') - # add line for comparison using second config - if self.comparison_curve: - self.add_comparison_curve(label=self.comparison_curve_label) - #self.add_arrow(self.sens_main) - # PRINT OPTIMUM RESULTS - - # print number of events - # for minimum field smearing - #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*np.min(self.B_error), self.sens_main.MagneticField.nominal_field) - #self.sens_main.MagneticField.usefixedvalue = True - #self.sens_main.MagneticField.default_systematic_smearing = sig - #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig # if the magnetic field uncertainties were configured above, set them back to the first value in the list if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): @@ -298,8 +292,9 @@ def InternalRun(self): logger.info('Main curve:') # set optimum density back - self.sens_main.CL90(Experiment={"number_density": rho_opt}) - logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) + #self.sens_main.CL90(Experiment={"number_density": rho_opt}) + rho = self.sens_main.Experiment.number_density + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho*(m**3))) logger.info("Loaded Q: {}".format(self.sens_main.loaded_q)) logger.info("Axial frequency for minimum detectable angle: {} MHz".format(self.sens_main.required_bw_axialfrequency/MHz)) logger.info("Total bandwidth: {} MHz".format(self.sens_main.required_bw/MHz)) @@ -309,14 +304,14 @@ def InternalRun(self): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) - self.sens_main.print_SNRs(rho_opt) - logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) - logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* + self.sens_main.print_SNRs(rho) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) + logger.info('T2 in Veff: {}'.format(rho*self.sens_main.effective_volume)) + logger.info('Total signal: {}'.format(rho*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* - rho_opt*self.sens_main.effective_volume* + rho*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) @@ -325,12 +320,18 @@ def InternalRun(self): # Optimize comparison curves over density if self.comparison_curve: + for i in range(len(self.sens_ref)): - limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit_ref) - rho_opt_ref = self.rhos[opt_ref] - #self.sens_ref[i].Experiment.number_density = rho_opt_ref - self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + + if self.optimize_comparison_density: + limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt_ref = np.argmin(limit_ref) + rho_opt_ref = self.rhos[opt_ref] + #self.sens_ref[i].Experiment.number_density = rho_opt_ref + self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + + + logger.info('Comparison curve:') logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) @@ -354,6 +355,11 @@ def InternalRun(self): self.sens_ref[i].print_statistics() self.sens_ref[i].print_systematics() + + self.add_comparison_curve(label=self.comparison_curve_label) + #self.add_arrow(self.sens_main) + + # save plot self.save(self.plot_path) @@ -541,6 +547,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_max_window = [] crlb_slope_zero_window = [] + temp_rho = deepcopy(sens.Experiment.number_density) for rho in self.rhos: limits.append(sens.CL90(Experiment={"number_density": rho})/eV) resolutions.append(sens.sigma_K_f_CRLB/meV) @@ -548,7 +555,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) - + sens.Experiment.number_density = temp_rho self.ax.plot(self.rhos*m**3, limits, **kwargs) logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) @@ -581,10 +588,10 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" sens.EffectiveVolume() sens.CavityPower() - limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] - opt = np.argmin(limit) - rho_opt = self.rhos[opt] - sens.Experiment.number_density = rho_opt + #limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] + #opt = np.argmin(limit) + #rho_opt = self.rhos[opt] + #sens.Experiment.number_density = rho_opt limit = sens.sensitivity()/eV**2 @@ -603,21 +610,22 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): - limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] - opt = np.argmin(limit) - rho_opt = self.rhos[opt] - sens.Experiment.number_density = rho_opt + limit = sens.sensitivity()/eV**2 + #opt = np.argmin(limit) + #rho_opt = self.rhos[opt] + #sens.Experiment.number_density = rho_opt - logger.info("Optimum density: {} /m^3".format(rho_opt*m**3)) + #logger.info("Optimum density: {} /m^3".format(rho_opt*m**3)) logger.info("Years: {}".format(sens.Experiment.livetime/year)) standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - self.ax.scatter([standard_exposure], [np.min(limit)], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([standard_exposure], limit, s=40, marker="d", zorder=20, **kwargs) limits = [] years = [] + temp_lt = deepcopy(sens.Experiment.livetime) for ex in self.exposures: lt = ex/sens.EffectiveVolume() years.append(lt/year) @@ -625,6 +633,8 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): limits.append(sens.sensitivity()/eV**2) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) + sens.Experiment.livetime = temp_lt + if sens.Experiment.atomic: gas = "T" else: @@ -707,6 +717,8 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): configured_magnetic_field = sens.MagneticField.nominal_field gamma = sens.T_endpoint/(me*c0**2) + 1 + temp_rho = deepcopy(sens.Experiment.number_density) + temp_field = deepcopy(sens.MagneticField.nominal_field) for freq in self.frequencies: magnetic_field = freq/(e/(2*np.pi*me)/gamma) sens.MagneticField.nominal_field = magnetic_field @@ -735,6 +747,12 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) noise_power.append(sens.noise_temp/K) + # set rho and f back + sens.Experiment.number_density = temp_rho + sens.MagneticField.nominal_field = temp_field + sens.CavityRadius() + sens.CavityPower() + self.ax2.plot(self.frequencies/Hz, limits, **kwargs) logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) @@ -764,8 +782,10 @@ def range(self, start, stop): return [(idx, cmap(norm(idx))) for idx in range(start, stop)] def save(self, savepath, **kwargs): + logger.info("Saving") if self.density_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + if not self.configure_sigma_theta_r: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) elif self.frequency_axis: if self.magnetic_field_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py new file mode 100644 index 00000000..f09e16ad --- /dev/null +++ b/test_analysis/LFA_Sensitivity.py @@ -0,0 +1,103 @@ +""" +Script to make Sensitivty plots for cavity experiments +Author: C. Claessens, T. Weiss +Date: October 6, 2023 +""" + + +from morpho.utilities import morphologging, parser +logger = morphologging.getLogger(__name__) + +from mermithid.processors.Sensitivity import CavitySensitivityCurveProcessor + +import numpy as np + +# Configuration for CCA Sensitivity vs. density plot +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "plot_path": "./lfa_sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": True, + "atomic_axis": False, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 20], + "density_range": [1e13,1e20], + "efficiency_range": [0.0001, 1], + #"density_range": [1e8, 1e12], + "main_curve_upper_label": r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", + "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", + #"comparison_curve_label": [#"Molecular, reaching target", + # "Atomic, alternative scenario 1"], + # #"Atomic, reaching target"], + # # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "comparison_curve": False, + "comparison_curve_colors": ["red"], + "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], + "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.4, + "upper_label_y_position": 4, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e14, #0.0002 + "plot_key_parameters": True, + "configure_sigma_theta_r": True, + "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction + } +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + + +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_sensitivity_vs_exposure_curve.pdf", + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "cavity": True, + "add_PhaseII": False, + "add_1year_1cav_point_to_last_ref": False, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "y_limits": [10e-3, 500], + "density_range": [1e12,1e19], + "exposure_range": [1e-11, 1e4], + "main_curve_upper_label": r"LFA", + "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", + "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_colors": ["blue", "darkred", "red"], + "optimize_main_density": False, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4 + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + + + + + From 49dc55c2ed6dfb25ac19b084394a5e22243cf338 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Mon, 12 Feb 2024 21:40:01 -0500 Subject: [PATCH 095/262] Remove unneeded division by 2 in SNR calcualtions --- mermithid/misc/SensitivityCavityFormulas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 78144af8..7e532cb1 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -532,10 +532,10 @@ def print_SNRs(self, rho_opt): tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) logger.info("tau_SNR: {}s".format(tau_snr/s)) eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) - SNR_1eV = 1/eV_bandwidth/2./tau_snr + SNR_1eV = 1/eV_bandwidth/tau_snr track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) - SNR_track_duration = track_duration/2./tau_snr - SNR_1ms = 0.001*s/2./tau_snr + SNR_track_duration = track_duration/tau_snr + SNR_1ms = 0.001*s/tau_snr logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) logger.info("Track duration: {}ms".format(track_duration/ms)) From 5016426b4a7e7c27c69cc5b7164b990de2239f05 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 12 Mar 2024 13:04:06 -0400 Subject: [PATCH 096/262] Testing LFA sensitivity options --- test_analysis/LFA_Sensitivity.py | 34 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index f09e16ad..600e112f 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -15,17 +15,18 @@ # Configuration for CCA Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", #"/termite/sensitivity_config_files/Config_LoverDof5_LFA_Experiment.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_density_curve.pdf", # optional - "figsize": (7.0,6), + "figsize": (7.0,5), #Was (7, 6) "track_length_axis": True, - "molecular_axis": True, + "molecular_axis": False, "atomic_axis": False, "density_axis": True, "cavity": True, - "y_limits": [2e-2, 20], + #"y_limits": [2e-2, 20], + "y_limits": [3e-1, 20], "density_range": [1e13,1e20], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], @@ -35,12 +36,12 @@ # "Atomic, alternative scenario 1"], # #"Atomic, reaching target"], # # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + #"goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": False, "comparison_curve_colors": ["red"], - "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + #"comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + # "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + # #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, @@ -59,8 +60,8 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_sensitivity_vs_exposure_curve.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_sensitivity_vs_exposure_curve_LoverD5.pdf", # optional "figsize": (10,6), "fontsize": 15, @@ -80,11 +81,14 @@ "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_colors": ["blue", "darkred", "red"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], + #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + #"comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_label": [r"Phase IV 150 MHz scenario"], + #"comparison_curve_colors": ["blue", "darkred", "red"], + "comparison_curve_colors": ["red"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, From ec59de5f45d81058f9449fba3f1ef37cd32d33dd Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 12 Mar 2024 10:31:29 -0700 Subject: [PATCH 097/262] starting the config file parameter documentation --- .../sensitivity_configurable_parameters.rst | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 documentation/sensitivity_configurable_parameters.rst diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst new file mode 100644 index 00000000..92ecc94d --- /dev/null +++ b/documentation/sensitivity_configurable_parameters.rst @@ -0,0 +1,35 @@ +------------------ +Configuring mermithid sensitivity calculation +------------------ + +The sensitivity calculation is configured using a configuration file. The configuration files for Project 8 live in the termite repository: https://github.com/project8/termite/tree/feature/sensitivity_config_files/sensitivity_config_files + + +Structure of a config file +-------------------------- + +Configuration files have several sections: + + +* Experiment +* Efficiency +* FrequencyExtraction +* DopplerBroadening +* MagneticField +* FinalStates +* MissingTracks +* PlasmaEffects + +Each section has a number of parameters that are used for the calcualtion of the sensitivty contribution of the respective section. + + +Parameters +---------- + +Below is a list of the parameters with a short description of what role they play in the calculation. + +**Experiment** + +* `L_over_D`: The ratio of the length of the cavity to the diameter of the cavity. Together with the frequency of the cavity, this parameter determines the length and volume of a single cavity. It also impacts the cavity Q-factor which is determined by the bandwidth needed to observe the axial frequency of the trapped electrons. +* `livetime`: Run duration of the experiment. Together with the total volume, efficiency, and the gas density, this determines the statistical power of the experiment. +* `n_cavities`: Number of identical cavities in the experimennt. This parameter multiplies the single cavity volume to give the total experimental volume. \ No newline at end of file From 35e4e5b14e3ee6ef6128bd72f496892526b582a8 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 12 Mar 2024 13:35:37 -0400 Subject: [PATCH 098/262] Added sensitivity documentation file with some systematics description --- documentation/sensitivity.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 documentation/sensitivity.rst diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst new file mode 100644 index 00000000..7ff0d8a8 --- /dev/null +++ b/documentation/sensitivity.rst @@ -0,0 +1,30 @@ +------------------ +Neutrino Mass Sensitivity Calculation +------------------ + + +Scripts used in sensitivity calculations +---------------------------------- + + +Analytic sensitivity formula +---------------------------------- + + +Systematic uncertainty contributions +---------------------------------- + +The following contributions to energy broadening of the beta spectrum are included: + +1. ``sigma_trans``: Translational Doppler broadening due to thermal motion of tritium atoms or molecules; +2. ``sigma_f``: Uncertainty on the start frequency of the event, after an axial frequency correction to account for the pitch angle. +3. ``sigma_B``: Energy broadening due to radial, azimuthal, and temporal varation of the magnetic field. This is the remaining broadening after any event-by-event corrections for the field have been performed. +4. ``sigma_Miss``: +5. ``sigma_Plasma``: + +In the list above, each variable that starts with ``sigma`` is a standard deviation of a distribution of energies. The measured spectrum is the convolution of the underlying beta spectrum with such distributions of energies. Each ``sigma`` has an associated ``delta``, which is the uncertainty on ``sigma`` from calibration, theory, and/or simulation (see :ref:`Analytic sensitivity formula`). For example, ``sigma_trans`` has an associated variable ``delta_sigma_trans``. + +In the script ``mermithid/mermithid/misc/SensitivityCavityFormulas.py``, lists of these energy broadening standard deviations (sigmas) and uncertainties on them (deltas) are returned by the ``get_systematics`` method of the ``CavitySensitivity`` class. + +Contributions 4 and 5 are simply inputted in the sensitivity configuration file; we do not yet have a way to calculate these contribution in mermithid. Contributions 1, 2, and 3 are described in more detail, below. + From 7ed5ac4c440b02d55ecf32e7bf2c5c821d20e932 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 12 Mar 2024 14:11:23 -0400 Subject: [PATCH 099/262] Finished systematic list; made systematics section headings --- documentation/sensitivity.rst | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 7ff0d8a8..662c8480 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -16,15 +16,29 @@ Systematic uncertainty contributions The following contributions to energy broadening of the beta spectrum are included: -1. ``sigma_trans``: Translational Doppler broadening due to thermal motion of tritium atoms or molecules; -2. ``sigma_f``: Uncertainty on the start frequency of the event, after an axial frequency correction to account for the pitch angle. +1. ``sigma_trans``: Translational Doppler broadening due to thermal motion of tritium atoms or molecules. +2. ``sigma_f``: Energy broadening due to mis-reconstruction of the start frequencies of events from the range of electron pitch angles (after axial frequency corrections), and from the process of fitting chirp tracks that have finite length and SNR to find where the tracks start. 3. ``sigma_B``: Energy broadening due to radial, azimuthal, and temporal varation of the magnetic field. This is the remaining broadening after any event-by-event corrections for the field have been performed. -4. ``sigma_Miss``: -5. ``sigma_Plasma``: +4. ``sigma_Miss``: Energy broadening due to the detection of events after one or more tracks have been missed. +5. ``sigma_Plasma``: Energy broadening due to plasma effects of the charged particles in the source volume of an atomic tritium experiment. -In the list above, each variable that starts with ``sigma`` is a standard deviation of a distribution of energies. The measured spectrum is the convolution of the underlying beta spectrum with such distributions of energies. Each ``sigma`` has an associated ``delta``, which is the uncertainty on ``sigma`` from calibration, theory, and/or simulation (see :ref:`Analytic sensitivity formula`). For example, ``sigma_trans`` has an associated variable ``delta_sigma_trans``. +In the list above, each variable that starts with ``sigma`` is a standard deviation of a distribution of energies. The measured spectrum is the convolution of the underlying beta spectrum with such distributions of energies. Asymmetries in these distributions are not accounted for in this approximate model. + +Each ``sigma`` has an associated ``delta``, which is the uncertainty on ``sigma`` from calibration, theory, and/or simulation (see :ref:`Analytic sensitivity formula`). For example, ``sigma_trans`` has an associated variable ``delta_sigma_trans``. In the script ``mermithid/mermithid/misc/SensitivityCavityFormulas.py``, lists of these energy broadening standard deviations (sigmas) and uncertainties on them (deltas) are returned by the ``get_systematics`` method of the ``CavitySensitivity`` class. -Contributions 4 and 5 are simply inputted in the sensitivity configuration file; we do not yet have a way to calculate these contribution in mermithid. Contributions 1, 2, and 3 are described in more detail, below. +Contributions 4 and 5 are simply inputted in the sensitivity configuration file; we do not yet have a way to calculate these contribution in mermithid. Contributions 1, 2, and 3 are calculated in mermithid, as described below. + + +Translational Doppler broadening (``sigma_trans``) +============================ + + +Track start frequency determination and pitch angle correction (``sigma_f``) +============================ + + +Radial, azimuthal, and temporal field broadening (``sigma_B``) +============================ From b2711287e41386c19d867976e23aa4c8260d98a2 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 12 Mar 2024 11:19:21 -0700 Subject: [PATCH 100/262] copied all parameters that are in LFA config file and started replacing values with descriptions. --- .../sensitivity_configurable_parameters.rst | 82 ++++++++++++++++++- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 92ecc94d..480764c8 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -4,6 +4,8 @@ Configuring mermithid sensitivity calculation The sensitivity calculation is configured using a configuration file. The configuration files for Project 8 live in the termite repository: https://github.com/project8/termite/tree/feature/sensitivity_config_files/sensitivity_config_files +Our main goal is the calcualtion of sensitivity in a cavity experiment. The configurations below are to be used for https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/misc/SensitivityCavityFormulas.py + Structure of a config file -------------------------- @@ -30,6 +32,80 @@ Below is a list of the parameters with a short description of what role they pla **Experiment** -* `L_over_D`: The ratio of the length of the cavity to the diameter of the cavity. Together with the frequency of the cavity, this parameter determines the length and volume of a single cavity. It also impacts the cavity Q-factor which is determined by the bandwidth needed to observe the axial frequency of the trapped electrons. -* `livetime`: Run duration of the experiment. Together with the total volume, efficiency, and the gas density, this determines the statistical power of the experiment. -* `n_cavities`: Number of identical cavities in the experimennt. This parameter multiplies the single cavity volume to give the total experimental volume. \ No newline at end of file +* ``L_over_D``: The ratio of the length of the cavity to the diameter of the cavity. Together with the frequency of the cavity, this parameter determines the length and volume of a single cavity. It also impacts the cavity Q-factor which is determined by the bandwidth needed to observe the axial frequency of the trapped electrons. +* ``livetime``: Run duration of the experiment. Together with the total volume, efficiency, and the gas density, this determines the statistical power of the experiment. +* ``n_cavities``: Number of identical cavities in the experimennt. This parameter multiplies the single cavity volume to give the total experimental volume. +* ``background_rate_per_ev``: Background rate per electronvolt for the entire experiment. It is not automultiplied by the number of channels (cavities) in the experiment. +* ``number_density``: The gas number density together with the total volume, livetime, and the efficiency determines the statistical power of the experiment. Gas density also determines the track length and therefore the frequency resolution. The sensitivity curve processor can optimize this parameter to maximize the sensitivity. In that case this number is overwritten in the calculation. +* ``sri_factor``: The statistical rate increase factor articifially increases the number of observed events. It is highly recommended to set it to 1. +* ``atomic``: If true the calculation is done for atomic tritium. If false moecular tritium is assumed. This affects the number of decays per gas molecule/atom (2 for molecular 1 for atomic), the track length in a given gas density (via electron scattering cross section), and the width of the final ground state. + + +**Efficiency** + +* ``usefixedvalue``: So far we only have a fixed efficiency implemented. Work is in progress of moving the efficiency calcualtion into the sensivity calculation. +* ``fixed_efficiency``: For example, set to roughly 2% for a 88deg minimum trapped pitch angle, assuming 100% detection efficiency of the trapped angles. + +Not yet used (work in progress): +* ``radial_efficiency``: Typically set to 0.67 from a calcualtion done for a 325MHz cavity with Halbach bite and radial cut on power of > 0.5 * maximum power. +* ``trapping_efficiency``: Not yet in use. + +**FrequencyExtraction** + +We use the CRLB for calculating the frequency resolution. The CRLB is calculated from the signal to noise ratio (SNR) and track length. The sensitivity calculations therefore include a calculation of the noise and signal power. + +* ``usefixedvalue``: If true all parameters below are ignored but ``default_systematic_smearing``. +* ``default_systematic_smearing``: Used if ``usefixedvalue`` is true. Units must be eV. +* ``default_systematic_uncertainty``: Used if ``usefixedvalue`` is true. Units must be eV +* ``usefixeduncertainty``: If true, the uncertainty on the frequency extraction is fixed to the value of ``fixed_relativ_uncertainty``. False will result in an error because no calculation is currently implemented. +* ``fixed_relativ_uncertainty``: If ``usefixeduncertainty`` is true, this relative value is used as the uncertainty on the frequency extraction. +* ``crlb_on_sidebands``: If true, the total resolution from frequency extraction includes the correction from axial field variation based on the CRLB resolution of sidebands. +* ``sideband_power_fraction``: Fraction of power in the sidebands. This is used to calculate the CRLB resolution of the sidebands. +* ``sideband_order``: The order of the sidebands used to calculate the axial field correction. 2 gives better precision but would require lower Q which in turn affects SNR. However, the sideband order is currently not included in the Q calculation. +* ``amplifier_temperature``: Used to calculate noise temperature. +* ``quantum_amp_efficiency``: Used to calculate noise temperature. +* ``att_cir_db``: Used to calculate noise temperature. +* ``att_line_db``: Used to calculate noise temperature. +* ``cavity_temperature``: Used to calculate noise temperature. Has to be compatible with the gas species (molecular tritium freezes below 30K). +* ``unloaded_q``: The unloaded Q of hte cavity. From the axial frequency of the minimum trapped angle (depending on ``L_over_D`` and the cavity frequency) and the bandwidth needed to observe it, the loaded Q-factor is calculated. The unloaded Q-factor is used to calculate the coupling and therefore impacts the SNR. +* ``minimum_angle_in_bandwidth``: Minimum pitch angle of which the axial frequency is contained in the bandwidth. Impacts the loaded Q and therefore the SNR. In the future it will be linked to the efficiency above. +* ``crlb_scaling_factor``: Arbitrary factor to scale the resolution. +* ``magnetic_field_smearing``: A magnetic field smearing in eV can be added here. + +**DopplerBroadening** + +* usefixedvalue = False +* gas_temperature = 85 * K +* gas_temperature_uncertainty = 1 * K +* fraction_uncertainty_on_doppler_broadening = 0.01 + + +**MagneticField** + +* usefixedvalue = False +* nominal_field = 36.9 * mT +* useinhomogeneity = True +* fraction_uncertainty_on_field_broadening = 0.01 #Applies to all parameters below +* sigma_meanb = 0.0 * ppm #Magnetic field instability (which is not fully corrected using live calibration) and unknown wiggles in the z-field profile, relative to a smooth trap shape. +* sigmae_r = 0.159 * eV #Energy broadening from radial field inhomogeneity that remains after radial reconstruction. Accounts for both the uncertainty on each electron's radius and the uncertainty on the radial field profile. +* sigmae_theta = 0.0 * eV #Energy broadening remaining after theta reconstruction, from electrons with lower pitch angles exploring high fields. Accounts for both the uncertainty on theta and uncertainties on the trap depth/boxiness. +* sigmae_phi = 0.0 * eV #Energy broadening from phi field inhomogeneity that remains after phi reconstruction. + +**FinalStates** + +* ground_state_width_uncertainty_fraction = 0.001 + + +**MissingTracks** + +* usefixedvalue = True +* default_systematic_smearing = 0.0 * eV +* default_systematic_uncertainty = 0.0 * eV + +**PlasmaEffects** + +* usefixedvalue = True +* default_systematic_smearing = 0.0 * eV +* default_systematic_uncertainty = 0.0 * eV + + From 9ca1e9840e80c76da28c4446d7d22959eaf64b96 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 12 Mar 2024 14:23:48 -0400 Subject: [PATCH 101/262] Small changes --- documentation/sensitivity.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 662c8480..5c7b5519 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -24,17 +24,18 @@ The following contributions to energy broadening of the beta spectrum are includ In the list above, each variable that starts with ``sigma`` is a standard deviation of a distribution of energies. The measured spectrum is the convolution of the underlying beta spectrum with such distributions of energies. Asymmetries in these distributions are not accounted for in this approximate model. -Each ``sigma`` has an associated ``delta``, which is the uncertainty on ``sigma`` from calibration, theory, and/or simulation (see :ref:`Analytic sensitivity formula`). For example, ``sigma_trans`` has an associated variable ``delta_sigma_trans``. +Each ``sigma`` has an associated ``delta``, which is the uncertainty on ``sigma`` from calibration, theory, and/or simulation (see the previous section). For example, ``sigma_trans`` has an associated variable ``delta_sigma_trans``. In the script ``mermithid/mermithid/misc/SensitivityCavityFormulas.py``, lists of these energy broadening standard deviations (sigmas) and uncertainties on them (deltas) are returned by the ``get_systematics`` method of the ``CavitySensitivity`` class. -Contributions 4 and 5 are simply inputted in the sensitivity configuration file; we do not yet have a way to calculate these contribution in mermithid. Contributions 1, 2, and 3 are calculated in mermithid, as described below. +Contributions 4 and 5 are simply inputted in the sensitivity configuration file; we do not yet have a way to calculate these in mermithid. Contributions 1, 2, and 3 are calculated in mermithid, as described below. Translational Doppler broadening (``sigma_trans``) ============================ + Track start frequency determination and pitch angle correction (``sigma_f``) ============================ From 85e38a4fcf3bfec3801a80bc3d44e4d289b97c48 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 12 Mar 2024 11:29:14 -0700 Subject: [PATCH 102/262] trying to include new documentation in index --- documentation/index.rst | 2 ++ documentation/sensitivity_configurable_parameters.rst | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/index.rst b/documentation/index.rst index c9ae02e5..bc953a3d 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -9,5 +9,7 @@ Contents: intro install contribute + sensitivity + sensitivity_configurable_parameters validation_log better_apidoc_out/modules diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 480764c8..5a9e9287 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -54,7 +54,7 @@ Not yet used (work in progress): We use the CRLB for calculating the frequency resolution. The CRLB is calculated from the signal to noise ratio (SNR) and track length. The sensitivity calculations therefore include a calculation of the noise and signal power. -* ``usefixedvalue``: If true all parameters below are ignored but ``default_systematic_smearing``. +* ``usefixedvalue``: If true all parameters below are ignored but ``default_systematic_smearing`` and ``default_systematic_uncertainty``. * ``default_systematic_smearing``: Used if ``usefixedvalue`` is true. Units must be eV. * ``default_systematic_uncertainty``: Used if ``usefixedvalue`` is true. Units must be eV * ``usefixeduncertainty``: If true, the uncertainty on the frequency extraction is fixed to the value of ``fixed_relativ_uncertainty``. False will result in an error because no calculation is currently implemented. From 320b1be4d1de185aa090a80e538e786f2befa0e1 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 12 Mar 2024 11:47:47 -0700 Subject: [PATCH 103/262] more parameter descriptions --- .../sensitivity_configurable_parameters.rst | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 5a9e9287..1b1dbcfc 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -74,38 +74,44 @@ We use the CRLB for calculating the frequency resolution. The CRLB is calculated **DopplerBroadening** -* usefixedvalue = False -* gas_temperature = 85 * K -* gas_temperature_uncertainty = 1 * K -* fraction_uncertainty_on_doppler_broadening = 0.01 +* ``usefixedvalue``: If True ``default_systematic_smearing`` and ``default_systematic_uncertainty`` are used. +* ``default_systematic_smearing``: Default systematic broadening for this category. Units must be eV. +* ``default_systematic_uncertainty``: Default systematic uncertainty for this category. Units must be eV. +* ``gas_temperature``: Temperature of the source gas. This should only be different from the cavity temperature if the gas is not in thermal equilibrium with the cavity. The gas temperature is used to calculate the Doppler broadening. +* ``gas_temperature_uncertainty``: Absolute uncertainty of the gas temperature. +* ``fraction_uncertainty_on_doppler_broadening``: Fractional uncertainty on the Doppler broadening. **MagneticField** -* usefixedvalue = False -* nominal_field = 36.9 * mT -* useinhomogeneity = True -* fraction_uncertainty_on_field_broadening = 0.01 #Applies to all parameters below -* sigma_meanb = 0.0 * ppm #Magnetic field instability (which is not fully corrected using live calibration) and unknown wiggles in the z-field profile, relative to a smooth trap shape. -* sigmae_r = 0.159 * eV #Energy broadening from radial field inhomogeneity that remains after radial reconstruction. Accounts for both the uncertainty on each electron's radius and the uncertainty on the radial field profile. -* sigmae_theta = 0.0 * eV #Energy broadening remaining after theta reconstruction, from electrons with lower pitch angles exploring high fields. Accounts for both the uncertainty on theta and uncertainties on the trap depth/boxiness. -* sigmae_phi = 0.0 * eV #Energy broadening from phi field inhomogeneity that remains after phi reconstruction. +* ``usefixedvalue``: If True ``default_systematic_smearing`` and ``default_systematic_uncertainty`` are used. +* ``default_systematic_smearing``: Default systematic broadening for this category. Units must be eV. +* ``default_systematic_uncertainty``: Default systematic uncertainty for this category. Units must be eV. +* ``nominal_field``: Determines the CRES and cavity TE011 mode frequency. The cavity dimensions are derived from this and ``L_over_D`` +* ``useinhomogeneity``: True +* ``fraction_uncertainty_on_field_broadening``: Fractional uncertainty on field inhomogeneity. Applies to all parameters below +* ``sigma_meanb``: Fixed input in eV. Magnetic field instability (which is not fully corrected using live calibration) and unknown wiggles in the z-field profile, relative to a smooth trap shape. +* ``sigmae_r``: Fixed input in eV. Energy broadening from radial field inhomogeneity that remains after radial reconstruction. Accounts for both the uncertainty on each electron's radius and the uncertainty on the radial field profile. +* ``sigmae_theta``: Fixed input in eV. Energy broadening remaining after theta reconstruction, from electrons with lower pitch angles exploring high fields. Accounts for both the uncertainty on theta and uncertainties on the trap depth/boxiness. +* ``sigmae_phi``: Fixed input in eV. Energy broadening from phi field inhomogeneity that remains after phi reconstruction. **FinalStates** -* ground_state_width_uncertainty_fraction = 0.001 +* ``ground_state_width_uncertainty_fraction``: Uncertainty on the ground state width. Recommended to use 0.001. +The sections below have so far not been used and are assumed to be negligible. + **MissingTracks** -* usefixedvalue = True -* default_systematic_smearing = 0.0 * eV -* default_systematic_uncertainty = 0.0 * eV +* ``usefixedvalue``: If True ``default_systematic_smearing`` and ``default_systematic_uncertainty`` are used. +* ``default_systematic_smearing``: Default systematic broadening for this category. Units must be eV. +* ``default_systematic_uncertainty``: Default systematic uncertainty for this category. Units must be eV. **PlasmaEffects** -* usefixedvalue = True -* default_systematic_smearing = 0.0 * eV -* default_systematic_uncertainty = 0.0 * eV +* ``usefixedvalue``: If True ``default_systematic_smearing`` and ``default_systematic_uncertainty`` are used. +* ``default_systematic_smearing``: Default systematic broadening for this category. Units must be eV. +* ``default_systematic_uncertainty``: Default systematic uncertainty for this category. Units must be eV. From b53156349f43941dae2fc707fe9649248732d3fa Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 15 Mar 2024 14:46:11 -0700 Subject: [PATCH 104/262] new processor for scanning arbitrary sensitivity parameter and optimizing over density for each parameter value --- .../SensitivityParameterScanProcessor.py | 423 ++++++++++++++++++ mermithid/processors/Sensitivity/__init__.py | 1 + test_analysis/Sensitivity_parameter_scan.py | 96 ++++ 3 files changed, 520 insertions(+) create mode 100644 mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py create mode 100644 test_analysis/Sensitivity_parameter_scan.py diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py new file mode 100644 index 00000000..b2c7781f --- /dev/null +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -0,0 +1,423 @@ +''' +Scan a parameter and calculate the sensitivity curve for each value of the parameter. +Author: C. Claessens +Date: 03/14/2024 + +More description +''' + +from __future__ import absolute_import + + +import matplotlib +import matplotlib.pyplot as plt +import numpy as np + + + +# Numericalunits is a package to handle units and some natural constants +# natural constants +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, mm +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day, ms, ns, s, Hz, kHz, MHz, GHz +ppm = 1e-6 +ppb = 1e-9 +deg = np.pi/180 + +# morpho imports +from morpho.utilities import morphologging, reader +from morpho.processors import BaseProcessor +from mermithid.misc.SensitivityFormulas import Sensitivity +from mermithid.misc.SensitivityCavityFormulas import CavitySensitivity + + +logger = morphologging.getLogger(__name__) + + + +__all__ = [] +__all__.append(__name__) + + +def return_var_name(variable): + for name in globals(): + if eval(name) == variable: + return name + + + +class SensitivityParameterScanProcessor(BaseProcessor): + ''' + Description + Args: + + Inputs: + + Output: + + ''' + def InternalConfigure(self, params): + ''' + Configure + ''' + # file paths + self.config_file_path = reader.read_param(params, 'config_file_path', "required") + self.plot_path = reader.read_param(params, 'plot_path', "required") + + + # options + self.scan_parameter_name = reader.read_param(params, 'scan_parameter_name', 'MagneticField.sigmae_r') + self.scan_parameter_range = reader.read_param(params, "scan_parameter_range", [0.1, 2.1]) + self.scan_parameter_steps = reader.read_param(params, "scan_parameter_steps", 3) + scan_parameter_unit = reader.read_param(params, "scan_parameter_unit", eV) + + + self.scan_parameter_unit_string = return_var_name(scan_parameter_unit) + print("Unit string: ", self.scan_parameter_unit_string) + self.scan_parameter_unit = scan_parameter_unit + + # main plot configurations + self.figsize = reader.read_param(params, 'figsize', (6,6)) + self.legend_location = reader.read_param(params, 'legend_location', 'upper left') + self.fontsize = reader.read_param(params, 'fontsize', 12) + self.plot_sensitivity_scan_on_log_scale = reader.read_param(params, 'plot_sensitivity_scan_on_log_scale', True) + + self.density_axis = reader.read_param(params, "density_axis", True) + self.track_length_axis = reader.read_param(params, 'track_length_axis', True) + self.atomic_axis = reader.read_param(params, 'atomic_axis', False) + self.molecular_axis = reader.read_param(params, 'molecular_axis', False) + + self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) + self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) + + self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) + self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) + self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) + self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) + + # key parameter plots + self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) + + if self.density_axis: + self.add_sens_line = self.add_density_sens_line + logger.info("Doing density lines") + + + + # goals + self.goals = reader.read_param(params, "goals", {}) + + + # setup sensitivities + + self.cavity = reader.read_param(params, 'cavity', True) + + if self.cavity: + self.sens_main = CavitySensitivity(self.config_file_path) + else: + self.sens_main = Sensitivity(self.config_file_path) + self.sens_main_is_atomic = self.sens_main.Experiment.atomic + + + # check atomic and molecular + if self.molecular_axis: + if not self.sens_main_is_atomic: + #self.molecular_sens = self.sens_main + logger.info("Main curve is molecular") + + + if self.atomic_axis: + if self.sens_main_is_atomic: + #self.atomic_sens = self.sens_main + logger.info("Main curve is atomic") + else: + logger.warn("No experiment is configured to be atomic") + + # densities, exposures, runtimes + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 1000)/m**3 + self.scan_parameter_values = np.linspace(self.scan_parameter_range[0], self.scan_parameter_range[1], self.scan_parameter_steps)*self.scan_parameter_unit + + return True + + + + def InternalRun(self): + + self.create_plot() + # add second and third x axis for track lengths + if self.density_axis and self.track_length_axis: + self.add_track_length_axis() + + # add goals + for key, value in self.goals.items(): + logger.info('Adding goal: {} = {}'.format(key, value)) + self.add_goal(value, key) + + + self.optimum_limits = [] + + for i in range(self.scan_parameter_steps): + parameter_value = self.scan_parameter_values[i] + + category, param = self.scan_parameter_name.split(".") + + self.sens_main.__dict__[category].__dict__[param] = parameter_value + setattr(self.sens_main, self.scan_parameter_name, parameter_value) + read_back = getattr(self.sens_main, self.scan_parameter_name) + logger.info(f"Setting {self.scan_parameter_name} to {parameter_value/self.scan_parameter_unit} and reading back: {read_back/ self.scan_parameter_unit}") + + + logger.info("Calculating cavity experiment") + self.sens_main.CavityVolume() + self.sens_main.EffectiveVolume() + self.sens_main.CavityPower() + + # optimize density + logger.info("Optimizing density") + limit = [self.sens_main.CL90(Experiment={"number_density": rho}) for rho in self.rhos] + self.optimum_limits.append(np.min(limit)) + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + self.sens_main.Experiment.number_density = rho_opt + + # add main curve + logger.info("Drawing main curve") + self.add_sens_line(self.sens_main, label=f"{self.scan_parameter_name} = {parameter_value/self.scan_parameter_unit:.2f} {self.scan_parameter_unit_string}") + + if self.make_key_parameter_plots: + logger.info("Making key parameter plots") + # First key parameter plot: Stat and Syst vs. density + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + + for n in self.rhos: + self.sens_main.Experiment.number_density = n + labels, sigmas, deltas = self.sens_main.get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_main.StatSens()) + syst_on_mbeta2.append(self.sens_main.SystSens()) + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig(f"{self.scan_parameter_name}_{parameter_value/self.scan_parameter_values}_stat_and_syst_vs_density.pdf") + + fig = plt.figure() + plt.loglog(self.rhos*m**3, sigma_startf/eV) + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") + plt.tight_layout() + plt.savefig(f"{self.scan_parameter_name}_{parameter_value/self.scan_parameter_unit}_resolution_from_CRLB_vs_density.pdf") + + + + + + logger.info('Experiment info:') + # set optimum density back + self.sens_main.CL90(Experiment={"number_density": rho_opt}) + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho_opt*(m**3))) + logger.info("Loaded Q: {}".format(self.sens_main.loaded_q)) + logger.info("Axial frequency for minimum detectable angle: {} MHz".format(self.sens_main.required_bw_axialfrequency/MHz)) + logger.info("Total bandwidth: {} MHz".format(self.sens_main.required_bw/MHz)) + logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) + logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) + + if self.sens_main.FrequencyExtraction.crlb_on_sidebands: + logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + + self.sens_main.print_SNRs(rho_opt) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) + logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) + logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* + self.sens_main.Experiment.LiveTime/ + self.sens_main.tau_tritium*2)) + logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* + rho_opt*self.sens_main.effective_volume* + self.sens_main.Experiment.LiveTime/ + self.sens_main.tau_tritium*2)) + + self.sens_main.print_statistics() + self.sens_main.print_systematics() + + self.save(self.plot_path.replace(".pdf", "_{}_scan.pdf".format(self.scan_parameter_name))) + + + # plot and print best limits + logger.info("Scan parameter: {}".format(self.scan_parameter_name)) + logger.info("Tested parameter values: {}".format(self.scan_parameter_values/self.scan_parameter_unit)) + logger.info("Best limits: {}".format(np.array(self.optimum_limits)/eV)) + + plt.figure(figsize=self.figsize) + #plt.title("Sensitivity vs. {}".format(self.scan_parameter_name)) + plt.plot(self.scan_parameter_values/self.scan_parameter_unit, np.array(self.optimum_limits)/eV, marker=".", label="Density optimized scenarios") + plt.xlabel(f"{self.scan_parameter_name} ({self.scan_parameter_unit_string})", fontsize=self.fontsize) + plt.ylabel(r"90% CL $m_\beta$ (eV)", fontsize=self.fontsize) + if self.plot_sensitivity_scan_on_log_scale: + plt.yscale("log") + plt.axhline(40e-3, label="Phase IV goal", color="grey", linestyle="-") + plt.legend(fontsize=self.fontsize) + plt.tight_layout() + plt.savefig(f"{self.scan_parameter_name}_optimum_limits.pdf") + plt.show() + + + + + + + return True + + + def create_plot(self): + # setup axis + plt.rcParams.update({'font.size': self.fontsize}) + self.fig, self.ax = plt.subplots(figsize=self.figsize) + ax = self.ax + ax.set_xscale("log") + ax.set_yscale("log") + if self.density_axis: + logger.info("Adding density axis") + ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) + + if self.atomic_axis and self.molecular_axis: + axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" + elif self.atomic_axis: + axis_label = r"(Atomic) number density $n\, \, (\mathrm{m}^{-3})$" + elif self.molecular_axis: + axis_label = r"(Molecular) number density $n\, \, (\mathrm{m}^{-3})$" + else: + axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" + + ax.set_xlabel(axis_label) + ax.set_ylim(self.ylim) + ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + + if self.make_key_parameter_plots: + + + if self.density_axis: + + self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,6)) + self.kp_fig.tight_layout() + + for ax in self.kp_ax: + ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) + ax.set_xscale("log") + ax.set_yscale("log") + axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" + ax.set_xlabel(axis_label) + + self.kp_ax[0].set_ylabel('Resolution (meV)') + self.kp_ax[1].set_ylabel('Track analysis length (ms)') + + + self.kp_fig.tight_layout() + + def add_track_length_axis(self): + + if self.atomic_axis: + + ax2 = self.ax.twiny() + ax2.set_xscale("log") + ax2.set_xlabel("(Atomic) track length (s)") + ax2.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) + + if self.molecular_axis: + ax3 = self.ax.twiny() + + if self.atomic_axis: + ax3.spines["top"].set_position(("axes", 1.2)) + ax3.set_frame_on(True) + ax3.patch.set_visible(False) + for sp in ax3.spines.values(): + sp.set_visible(False) + ax3.spines["top"].set_visible(True) + + ax3.set_xscale("log") + ax3.set_xlabel("(Molecular) track length (s)") + ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) + + else: + logger.warning("No track length axis added since neither atomic nor molecular was requested") + self.fig.tight_layout() + + + + def add_goal(self, value, label): + self.ax.axhline(value, color="gray", ls="--") + self.ax.text(self.goal_x_pos, self.goals_y_rel_position*value, label) + + def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): + limits = [] + resolutions = [] + crlb_window = [] + crlb_max_window = [] + crlb_slope_zero_window = [] + + for rho in self.rhos: + limits.append(sens.CL90(Experiment={"number_density": rho})/eV) + resolutions.append(sens.sigma_K_f_CRLB/meV) + crlb_window.append(sens.best_time_window/ms) + crlb_max_window.append(sens.time_window/ms) + crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) + + + self.ax.plot(self.rhos*m**3, limits, **kwargs) + rho_opt = self.rhos[np.argmin(limits)] + self.sens_main.Experiment.number_density = rho_opt + logger.info('Minimum limit at {}: {}'.format(rho_opt*m**3, np.min(limits))) + + if self.make_key_parameter_plots and plot_key_params: + self.kp_ax[0].plot(self.rhos*m**3, resolutions, **kwargs) + + self.kp_ax[1].plot(self.rhos*m**3, crlb_max_window, color='red', marker='.') + self.kp_ax[1].plot(self.rhos*m**3, crlb_slope_zero_window, color='green', marker='.') + self.kp_ax[1].plot(self.rhos*m**3, crlb_window, linestyle="--", marker='.', **kwargs) + return limits + + + + def add_text(self, x, y, text, color="k"): #, fontsize=9.5 + self.ax.text(x, y, text, color=color) + + def range(self, start, stop): + cmap = matplotlib.cm.get_cmap('Spectral') + norm = matplotlib.colors.Normalize(vmin=start, vmax=stop) + return [(idx, cmap(norm(idx))) for idx in range(start, stop)] + + def save(self, savepath, **kwargs): + if self.density_axis: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + elif self.frequency_axis: + if self.magnetic_field_axis: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) + else: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 )) + else: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.89,0.97)) + + self.fig.tight_layout() + #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) + metadata = {"Author": "p8/mermithid", + "Title": "Neutrino mass sensitivity", + "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero." + } + #"Keywords": keywords} + if savepath is not None: + self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=300, metadata=metadata) + self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) + + if self.make_key_parameter_plots: + self.kp_fig.savefig('key_parameters.pdf') + + diff --git a/mermithid/processors/Sensitivity/__init__.py b/mermithid/processors/Sensitivity/__init__.py index 4c973521..918dfef2 100644 --- a/mermithid/processors/Sensitivity/__init__.py +++ b/mermithid/processors/Sensitivity/__init__.py @@ -7,3 +7,4 @@ from .CavitySensitivityCurveProcessor import CavitySensitivityCurveProcessor from .AnalyticSensitivityEstimation import AnalyticSensitivityEstimation from .ConstantSensitivityParameterPlots import ConstantSensitivityParameterPlots +from .SensitivityParameterScanProcessor import SensitivityParameterScanProcessor diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py new file mode 100644 index 00000000..b962b36a --- /dev/null +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -0,0 +1,96 @@ +""" +Script to make Sensitivty plots for cavity experiments +Author: C. Claessens, T. Weiss +Date: October 6, 2023 +""" + + +from morpho.utilities import morphologging, parser +logger = morphologging.getLogger(__name__) + +from mermithid.processors.Sensitivity import SensitivityParameterScanProcessor + +import numpy as np +from numericalunits import eV +deg = np.pi/180 + + + + +# Configuration for Sensitivity vs. density plot +# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + #"density_range": [1e8, 1e12], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "scan_parameter_name": "MagneticField.sigmae_r", + "scan_parameter_range": [0.1, 5], + "scan_parameter_steps": 3, + "scan_parameter_unit": eV, + "plot_sensitivity_scan_on_log_scale": True, + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": False + } +#sens_curve = SensitivityParameterScanProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + +# Configuration for Sensitivity vs. density plot +# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_density_curve.pdf", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + #"density_range": [1e8, 1e12], + "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "scan_parameter_name": "FrequencyExtraction.minimum_angle_in_bandwidth", + "scan_parameter_range": [83, 89], + "scan_parameter_steps": 3, + "scan_parameter_unit": deg, + "plot_sensitivity_scan_on_log_scale": True, + #"sigmae_theta_r": 0.159, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 4e14, #4e14, #0.02, #1e14, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": False + } +sens_curve = SensitivityParameterScanProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + + + + + + + + + + + From 8ea753b239841ae1d44394598ad561bce5d62b33 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 15 Mar 2024 15:10:29 -0700 Subject: [PATCH 105/262] made radial and detection efficiency configurable --- mermithid/misc/SensitivityCavityFormulas.py | 10 ++++++++-- test_analysis/Cavity_Sensitivity_analysis.py | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index 57379829..efba87d9 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -251,8 +251,14 @@ def EffectiveVolume(self): if self.Efficiency.usefixedvalue: self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency else: - # trapping efficiecny is currently configured. replace with box trap calculation - self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.PitchDependentTrappingEfficiency() + # radial and detection efficiency are configured in the config file + logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) + logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + + self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() + logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor return self.effective_volume diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index a226cdcd..747ea5c1 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -180,9 +180,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() # Configuration for Sensitivity vs. density plot for best possible molecular scenario From 79cb3637f6660b4b6f089563d49df3b8c57e9723 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 15 Mar 2024 15:20:41 -0700 Subject: [PATCH 106/262] updated documentation to reflect update in efficiency calculation. --- documentation/sensitivity_configurable_parameters.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 1b1dbcfc..4fee6ea1 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -43,12 +43,10 @@ Below is a list of the parameters with a short description of what role they pla **Efficiency** -* ``usefixedvalue``: So far we only have a fixed efficiency implemented. Work is in progress of moving the efficiency calcualtion into the sensivity calculation. +* ``usefixedvalue``: If true, fixed efficiency is used. If false, the efficiency is the product of radial, detection, and trapping efficienc. The trapping efficiency is calculated from the minimum pitch angle. * ``fixed_efficiency``: For example, set to roughly 2% for a 88deg minimum trapped pitch angle, assuming 100% detection efficiency of the trapped angles. - -Not yet used (work in progress): * ``radial_efficiency``: Typically set to 0.67 from a calcualtion done for a 325MHz cavity with Halbach bite and radial cut on power of > 0.5 * maximum power. -* ``trapping_efficiency``: Not yet in use. +* ``detection_efficiency``: Fraction of events that is not detected. **FrequencyExtraction** From fc4bdbdd64f85d7cb4a1166140b70b8fc6668f05 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 15 Mar 2024 15:38:44 -0700 Subject: [PATCH 107/262] removed parameter category from plot labels and filenames --- .../Sensitivity/SensitivityParameterScanProcessor.py | 10 +++++----- test_analysis/Sensitivity_parameter_scan.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index b2c7781f..55b30ca7 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -184,7 +184,7 @@ def InternalRun(self): # add main curve logger.info("Drawing main curve") - self.add_sens_line(self.sens_main, label=f"{self.scan_parameter_name} = {parameter_value/self.scan_parameter_unit:.2f} {self.scan_parameter_unit_string}") + self.add_sens_line(self.sens_main, label=f"{param} = {parameter_value/self.scan_parameter_unit:.2f} {self.scan_parameter_unit_string}") if self.make_key_parameter_plots: logger.info("Making key parameter plots") @@ -247,7 +247,7 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() - self.save(self.plot_path.replace(".pdf", "_{}_scan.pdf".format(self.scan_parameter_name))) + self.save(self.plot_path.replace(".pdf", "_for_{}_scan.pdf".format(param))) # plot and print best limits @@ -258,14 +258,14 @@ def InternalRun(self): plt.figure(figsize=self.figsize) #plt.title("Sensitivity vs. {}".format(self.scan_parameter_name)) plt.plot(self.scan_parameter_values/self.scan_parameter_unit, np.array(self.optimum_limits)/eV, marker=".", label="Density optimized scenarios") - plt.xlabel(f"{self.scan_parameter_name} ({self.scan_parameter_unit_string})", fontsize=self.fontsize) + plt.xlabel(f"{param} ({self.scan_parameter_unit_string})", fontsize=self.fontsize) plt.ylabel(r"90% CL $m_\beta$ (eV)", fontsize=self.fontsize) if self.plot_sensitivity_scan_on_log_scale: plt.yscale("log") plt.axhline(40e-3, label="Phase IV goal", color="grey", linestyle="-") plt.legend(fontsize=self.fontsize) plt.tight_layout() - plt.savefig(f"{self.scan_parameter_name}_optimum_limits.pdf") + plt.savefig(f"{param}_scan_optimum_limits.pdf") plt.show() @@ -347,7 +347,7 @@ def add_track_length_axis(self): ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, self.sens_main.track_length(self.rhos[-1])/s) - else: + if not self.atomic_axis and not self.molecular_axis: logger.warning("No track length axis added since neither atomic nor molecular was requested") self.fig.tight_layout() diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index b962b36a..41c7ae70 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -72,7 +72,7 @@ "scan_parameter_range": [83, 89], "scan_parameter_steps": 3, "scan_parameter_unit": deg, - "plot_sensitivity_scan_on_log_scale": True, + "plot_sensitivity_scan_on_log_scale": False, #"sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, From d8fc9a77520e416b989f5de83d040d4a820b6bc5 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Wed, 20 Mar 2024 10:14:48 -0400 Subject: [PATCH 108/262] Small additions to sensitivity documentation file --- documentation/sensitivity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 5c7b5519..74d189c8 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -33,7 +33,7 @@ Contributions 4 and 5 are simply inputted in the sensitivity configuration file; Translational Doppler broadening (``sigma_trans``) ============================ - +The translational Doppler broadening is Track start frequency determination and pitch angle correction (``sigma_f``) From f8768bf38f6d93e0aabf4b8585cd72640698dd01 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 21 Mar 2024 15:33:34 -0400 Subject: [PATCH 109/262] Initial 1 GHz LFA scenario optimization and plots --- test_analysis/LFA_Sensitivity.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index 600e112f..bbdc262b 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -15,7 +15,7 @@ # Configuration for CCA Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", #"/termite/sensitivity_config_files/Config_LoverDof5_LFA_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LoverDof5_LFA_Experiment.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_density_curve.pdf", # optional @@ -30,8 +30,8 @@ "density_range": [1e13,1e20], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"$\sigma^B_\mathrm{corr} = 1\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", - "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.16\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", + "main_curve_upper_label": r"$\sigma^B_\mathrm{corr} = 0.5\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", + "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.1\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", #"comparison_curve_label": [#"Molecular, reaching target", # "Atomic, alternative scenario 1"], # #"Atomic, reaching target"], @@ -51,7 +51,7 @@ "goals_x_position": 1.2e14, #0.0002 "plot_key_parameters": True, "configure_sigma_theta_r": True, - "sigmae_theta_r": np.linspace(0.16, 1., 10), #in eV, energy broadening from theta and r reconstruction + "sigmae_theta_r": np.linspace(0.1, 0.5, 10), #in eV, energy broadening from theta and r reconstruction } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -60,8 +60,8 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_sensitivity_vs_exposure_curve_LoverD5.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_sensitivity_vs_exposure_curve_1GHz.pdf", # optional "figsize": (10,6), "fontsize": 15, From 2ffb3781d1c40dae9a017bff19ab8154e0232c17 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 21 Mar 2024 12:35:09 -0700 Subject: [PATCH 110/262] small updates in parameter descriptions --- documentation/sensitivity_configurable_parameters.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 4fee6ea1..81d55e6b 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -4,8 +4,8 @@ Configuring mermithid sensitivity calculation The sensitivity calculation is configured using a configuration file. The configuration files for Project 8 live in the termite repository: https://github.com/project8/termite/tree/feature/sensitivity_config_files/sensitivity_config_files -Our main goal is the calcualtion of sensitivity in a cavity experiment. The configurations below are to be used for https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/misc/SensitivityCavityFormulas.py - +Our main goal is the calcualtion of sensitivity in a cavity experiment. The configurations below are to be used for the CavitySensitivity class in https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/misc/SensitivityCavityFormulas.py +This class is used by the CavitySensitivityCurveProcessor and the SensitivityParameterScanProcessor. Structure of a config file -------------------------- @@ -32,13 +32,13 @@ Below is a list of the parameters with a short description of what role they pla **Experiment** -* ``L_over_D``: The ratio of the length of the cavity to the diameter of the cavity. Together with the frequency of the cavity, this parameter determines the length and volume of a single cavity. It also impacts the cavity Q-factor which is determined by the bandwidth needed to observe the axial frequency of the trapped electrons. +* ``L_over_D``: The ratio of the length of the cavity to the diameter of the cavity. Together with the frequency of the cavity's TE011 mode, this parameter determines the length and volume of a single cavity. It also impacts the cavity Q-factor which is determined by the bandwidth needed to observe the axial frequency of the trapped electrons. * ``livetime``: Run duration of the experiment. Together with the total volume, efficiency, and the gas density, this determines the statistical power of the experiment. * ``n_cavities``: Number of identical cavities in the experimennt. This parameter multiplies the single cavity volume to give the total experimental volume. * ``background_rate_per_ev``: Background rate per electronvolt for the entire experiment. It is not automultiplied by the number of channels (cavities) in the experiment. * ``number_density``: The gas number density together with the total volume, livetime, and the efficiency determines the statistical power of the experiment. Gas density also determines the track length and therefore the frequency resolution. The sensitivity curve processor can optimize this parameter to maximize the sensitivity. In that case this number is overwritten in the calculation. -* ``sri_factor``: The statistical rate increase factor articifially increases the number of observed events. It is highly recommended to set it to 1. -* ``atomic``: If true the calculation is done for atomic tritium. If false moecular tritium is assumed. This affects the number of decays per gas molecule/atom (2 for molecular 1 for atomic), the track length in a given gas density (via electron scattering cross section), and the width of the final ground state. +* ``sri_factor``: The statistical rate increase factor articifially increases the number of observed events (it multiplies the total efficiency). It is highly recommended to set it to 1. +* ``atomic``: If true, the calculation is done for atomic tritium. If false, moecular tritium is assumed. This affects the number of decays per gas molecule/atom (2 for molecular 1 for atomic), the track length in a given gas density (via electron scattering cross section), and the width of the final ground state. **Efficiency** From d405c401b6757cc5a6cf3bae7b482617eb2aef81 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 21 Mar 2024 12:52:56 -0700 Subject: [PATCH 111/262] added script description. not sure if links work. --- documentation/sensitivity.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 74d189c8..485478b3 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -6,6 +6,22 @@ Neutrino Mass Sensitivity Calculation Scripts used in sensitivity calculations ---------------------------------- +The mermithid sensitivity calculation is written in python and a python script can be used to configure the calculation and make sensitivity plots. +Mermithid has several processors to make this a lot esaier. For cavity sensitivity calculations that is the `CavitySensitivityCurveProcessor`_. + +.. _CavitySensitivityCurveProcessor: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py + +Mermithid processors are all designed to be used in the same way: +1. Define a dictionary with the processors configurable parameters +2. Instantiate a processor and pass the configuration dictionary to it's ``Configure()`` method. +3. Call the processors ``Run()`` method to make it perform it's task. + +In `mermithid/tests`_ the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor`` the ``CavitySensitivityCurveProcessor`` is used to calculate and plot the sensitivity of a cavity experiment to the neutrino mass as a function of gas density. +Other working examples for creating sensivitiy plots vs. frequency or exposure can be found in `mermithid/test_analyses/Cavity_Sensitivity_analysis.py`_. + +.. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/tests +.. _mermithid/test_analyses/Cavity_Sensitivity_analysis.py: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/test_analysis/Cavity_Sensitivity_analysis.py + Analytic sensitivity formula ---------------------------------- From c0e530457e3a0f66470922adb60c30f085f0cc2e Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 21 Mar 2024 15:58:12 -0400 Subject: [PATCH 112/262] Added initial description of translational Doppler broadening --- documentation/sensitivity.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 74d189c8..fe41b0cf 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -33,7 +33,20 @@ Contributions 4 and 5 are simply inputted in the sensitivity configuration file; Translational Doppler broadening (``sigma_trans``) ============================ -The translational Doppler broadening is +The thermal translational motion of tritium atoms causes a Doppler broadening of the $\beta$ energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: + +1. Manually input values for ``sigma_trans`` and its uncertainty ``delta_trans``, calculated outside of mermithid. This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. + +2. Have mermithid calculate ``sigma_trans``, for you (default option). + - For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. + - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. + +Calculation of ``sigma_trans`` for option 2: See https://www.overleaf.com/project/5de3e02edd267500011b8cc4. +(Will copy to here and format.) + +Calculation of ``delta_trans`` for option 2, with atomic T: See https://www.overleaf.com/project/5de3e02edd267500011b8cc4. +(Will copy to here and format.) + Track start frequency determination and pitch angle correction (``sigma_f``) From 8e90b0583d1676dd51af32c3e7a0e02c4b362c59 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 21 Mar 2024 16:03:19 -0400 Subject: [PATCH 113/262] Tweaks --- documentation/sensitivity.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index a856793c..e25a5b9d 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -13,8 +13,8 @@ Mermithid has several processors to make this a lot esaier. For cavity sensitivi Mermithid processors are all designed to be used in the same way: 1. Define a dictionary with the processors configurable parameters -2. Instantiate a processor and pass the configuration dictionary to it's ``Configure()`` method. -3. Call the processors ``Run()`` method to make it perform it's task. +2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method. +3. Call the processors ``Run()`` method to make it perform its task. In `mermithid/tests`_ the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor`` the ``CavitySensitivityCurveProcessor`` is used to calculate and plot the sensitivity of a cavity experiment to the neutrino mass as a function of gas density. Other working examples for creating sensivitiy plots vs. frequency or exposure can be found in `mermithid/test_analyses/Cavity_Sensitivity_analysis.py`_. @@ -51,17 +51,16 @@ Translational Doppler broadening (``sigma_trans``) ============================ The thermal translational motion of tritium atoms causes a Doppler broadening of the $\beta$ energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: -1. Manually input values for ``sigma_trans`` and its uncertainty ``delta_trans``, calculated outside of mermithid. This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. +1. Manually input values for `sigma_trans` and its uncertainty ``delta_trans``, calculated outside of mermithid. + This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. 2. Have mermithid calculate ``sigma_trans``, for you (default option). - - For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. - - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. + - For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. +- For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. -Calculation of ``sigma_trans`` for option 2: See https://www.overleaf.com/project/5de3e02edd267500011b8cc4. -(Will copy to here and format.) +Calculation of ``sigma_trans`` for option 2: -Calculation of ``delta_trans`` for option 2, with atomic T: See https://www.overleaf.com/project/5de3e02edd267500011b8cc4. -(Will copy to here and format.) +Calculation of ``delta_trans`` for option 2, with atomic T: From e67d1e7a2749968ee5e16deaf7cbd1f96604a227 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 21 Mar 2024 16:07:20 -0400 Subject: [PATCH 114/262] Try math rendering --- documentation/sensitivity.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index e25a5b9d..9aceafc0 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -51,14 +51,18 @@ Translational Doppler broadening (``sigma_trans``) ============================ The thermal translational motion of tritium atoms causes a Doppler broadening of the $\beta$ energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: -1. Manually input values for `sigma_trans` and its uncertainty ``delta_trans``, calculated outside of mermithid. - This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. +1. Manually input values for ``sigma_trans`` and its uncertainty ``delta_trans``, calculated outside of mermithid. +This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. 2. Have mermithid calculate ``sigma_trans``, for you (default option). - - For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. +- For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. Calculation of ``sigma_trans`` for option 2: +For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation +.. math:: :name:eq:sigtrans \sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}, +where :math:`m_T` is the mass of tritium and :math:`T` is the gas temperature. + Calculation of ``delta_trans`` for option 2, with atomic T: From 3066a7fd860e7306002babc211f9ff116be94f55 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 21 Mar 2024 16:10:24 -0400 Subject: [PATCH 115/262] Change formatting of bullets --- documentation/sensitivity.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 9aceafc0..547aad2b 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -55,8 +55,8 @@ The thermal translational motion of tritium atoms causes a Doppler broadening of This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. 2. Have mermithid calculate ``sigma_trans``, for you (default option). -- For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. -- For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. + - For an atomic tritium experiment, mermithid will also calculate ``delta_trans``, conservatively assuming that the gas temperature uncertainty will be determined simply by our knowledge that the gas is not so hot that it escapes the atom trap. These calculations for an atomic experiment are described further, below. + - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. Calculation of ``sigma_trans`` for option 2: For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation From 10cc1604f67c15f44a2f7293037716c98e848128 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 21 Mar 2024 15:15:43 -0700 Subject: [PATCH 116/262] minor changes --- documentation/sensitivity.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 485478b3..a58af649 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -6,7 +6,7 @@ Neutrino Mass Sensitivity Calculation Scripts used in sensitivity calculations ---------------------------------- -The mermithid sensitivity calculation is written in python and a python script can be used to configure the calculation and make sensitivity plots. +The mermithid sensitivity calculations are written in python and a python script can be used to configure the calculation and make sensitivity plots. Mermithid has several processors to make this a lot esaier. For cavity sensitivity calculations that is the `CavitySensitivityCurveProcessor`_. .. _CavitySensitivityCurveProcessor: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -16,10 +16,10 @@ Mermithid processors are all designed to be used in the same way: 2. Instantiate a processor and pass the configuration dictionary to it's ``Configure()`` method. 3. Call the processors ``Run()`` method to make it perform it's task. -In `mermithid/tests`_ the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor`` the ``CavitySensitivityCurveProcessor`` is used to calculate and plot the sensitivity of a cavity experiment to the neutrino mass as a function of gas density. +In `mermithid/tests`_ the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor`` the ``CavitySensitivityCurveProcessor`` is used as described above to calculate and plot the sensitivity of a cavity experiment to the neutrino mass as a function of gas density. Other working examples for creating sensivitiy plots vs. frequency or exposure can be found in `mermithid/test_analyses/Cavity_Sensitivity_analysis.py`_. -.. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/tests +.. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/tests .. _mermithid/test_analyses/Cavity_Sensitivity_analysis.py: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/test_analysis/Cavity_Sensitivity_analysis.py @@ -27,6 +27,7 @@ Analytic sensitivity formula ---------------------------------- + Systematic uncertainty contributions ---------------------------------- From 0fd3eb5c36061e3a400697438a7bdd58d8653a72 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 21 Mar 2024 16:51:45 -0700 Subject: [PATCH 117/262] improved and cleaned sensitivity scan processor and script. results are saved in results dictionary. --- .../SensitivityParameterScanProcessor.py | 109 ++++++++---------- test_analysis/Sensitivity_parameter_scan.py | 39 +++---- 2 files changed, 68 insertions(+), 80 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 55b30ca7..9547937a 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -12,6 +12,7 @@ import matplotlib import matplotlib.pyplot as plt import numpy as np +import os @@ -91,11 +92,7 @@ def InternalConfigure(self, params): self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) - self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) - self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) - self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) - self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) - self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) + # key parameter plots self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) @@ -108,6 +105,8 @@ def InternalConfigure(self, params): # goals self.goals = reader.read_param(params, "goals", {}) + self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) # setup sensitivities @@ -136,7 +135,7 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 1000)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 self.scan_parameter_values = np.linspace(self.scan_parameter_range[0], self.scan_parameter_range[1], self.scan_parameter_steps)*self.scan_parameter_unit return True @@ -145,27 +144,30 @@ def InternalConfigure(self, params): def InternalRun(self): - self.create_plot() + self.create_plot(self.scan_parameter_values/self.scan_parameter_unit) # add second and third x axis for track lengths if self.density_axis and self.track_length_axis: self.add_track_length_axis() - # add goals + # add goals to density plot for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) self.add_goal(value, key) self.optimum_limits = [] + self.optimum_rhos = [] + - for i in range(self.scan_parameter_steps): + for i, color in self.range(self.scan_parameter_values/self.scan_parameter_unit): parameter_value = self.scan_parameter_values[i] category, param = self.scan_parameter_name.split(".") self.sens_main.__dict__[category].__dict__[param] = parameter_value - setattr(self.sens_main, self.scan_parameter_name, parameter_value) - read_back = getattr(self.sens_main, self.scan_parameter_name) + read_back = self.sens_main.__dict__[category].__dict__[param] + #setattr(self.sens_main, self.scan_parameter_name, parameter_value) + #read_back = getattr(self.sens_main, self.scan_parameter_name) logger.info(f"Setting {self.scan_parameter_name} to {parameter_value/self.scan_parameter_unit} and reading back: {read_back/ self.scan_parameter_unit}") @@ -180,11 +182,13 @@ def InternalRun(self): self.optimum_limits.append(np.min(limit)) opt = np.argmin(limit) rho_opt = self.rhos[opt] + self.optimum_rhos.append(rho_opt) self.sens_main.Experiment.number_density = rho_opt # add main curve logger.info("Drawing main curve") - self.add_sens_line(self.sens_main, label=f"{param} = {parameter_value/self.scan_parameter_unit:.2f} {self.scan_parameter_unit_string}") + label = f"{param} = {parameter_value/self.scan_parameter_unit:.2f} {self.scan_parameter_unit_string}" + self.add_sens_line(self.sens_main, label=label, color=color) if self.make_key_parameter_plots: logger.info("Making key parameter plots") @@ -207,15 +211,9 @@ def InternalRun(self): plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") plt.legend() plt.tight_layout() - plt.savefig(f"{self.scan_parameter_name}_{parameter_value/self.scan_parameter_values}_stat_and_syst_vs_density.pdf") - - fig = plt.figure() - plt.loglog(self.rhos*m**3, sigma_startf/eV) - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") - plt.tight_layout() - plt.savefig(f"{self.scan_parameter_name}_{parameter_value/self.scan_parameter_unit}_resolution_from_CRLB_vs_density.pdf") + plt.savefig(os.path.join(self.plot_path, f"{param}_{parameter_value/self.scan_parameter_unit}_stat_and_syst_vs_density.pdf")) + @@ -247,10 +245,14 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() - self.save(self.plot_path.replace(".pdf", "_for_{}_scan.pdf".format(param))) + self.save("sensitivity_vs_density_for_{}_scan.pdf".format(param)) # plot and print best limits + self.results = {"scan_parameter": self.scan_parameter_name, "scan parameter_unit": self.scan_parameter_unit_string, + "scan_parameter_values": self.scan_parameter_values, "optimum_limits_eV": np.array(self.optimum_limits)/eV, + "optimum_densities_/m3": np.array(self.optimum_rhos)*(m**3)} + logger.info("Scan parameter: {}".format(self.scan_parameter_name)) logger.info("Tested parameter values: {}".format(self.scan_parameter_values/self.scan_parameter_unit)) logger.info("Best limits: {}".format(np.array(self.optimum_limits)/eV)) @@ -262,10 +264,13 @@ def InternalRun(self): plt.ylabel(r"90% CL $m_\beta$ (eV)", fontsize=self.fontsize) if self.plot_sensitivity_scan_on_log_scale: plt.yscale("log") - plt.axhline(40e-3, label="Phase IV goal", color="grey", linestyle="-") + + for key, value in self.goals.items(): + logger.info('Adding goal: {} = {}'.format(key, value)) + plt.axhline(value, label=key, color="grey", linestyle="--") plt.legend(fontsize=self.fontsize) plt.tight_layout() - plt.savefig(f"{param}_scan_optimum_limits.pdf") + plt.savefig(os.path.join(self.plot_path, f"{param}_scan_optimum_limits.pdf")) plt.show() @@ -276,7 +281,7 @@ def InternalRun(self): return True - def create_plot(self): + def create_plot(self, param_range=[]): # setup axis plt.rcParams.update({'font.size': self.fontsize}) self.fig, self.ax = plt.subplots(figsize=self.figsize) @@ -299,27 +304,17 @@ def create_plot(self): ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) ax.set_ylabel(r"90% CL $m_\beta$ (eV)") - - if self.make_key_parameter_plots: - - if self.density_axis: - - self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,6)) - self.kp_fig.tight_layout() + if len(param_range)>4: + # add colorbar with colors from self.range + cmap = matplotlib.cm.get_cmap('Spectral') + norm = matplotlib.colors.Normalize(vmin=np.min(param_range), vmax=np.max(param_range)) + sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm) + sm.set_array([]) + self.fig.colorbar(sm, ticks=np.round(param_range, 2), label=f"{self.scan_parameter_name} ({self.scan_parameter_unit_string})") - for ax in self.kp_ax: - ax.set_xlim(self.rhos[0]*m**3, self.rhos[-1]*m**3) - ax.set_xscale("log") - ax.set_yscale("log") - axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" - ax.set_xlabel(axis_label) - - self.kp_ax[0].set_ylabel('Resolution (meV)') - self.kp_ax[1].set_ylabel('Track analysis length (ms)') - + - self.kp_fig.tight_layout() def add_track_length_axis(self): @@ -390,34 +385,32 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): def add_text(self, x, y, text, color="k"): #, fontsize=9.5 self.ax.text(x, y, text, color=color) - def range(self, start, stop): + def range(self, param_range): cmap = matplotlib.cm.get_cmap('Spectral') - norm = matplotlib.colors.Normalize(vmin=start, vmax=stop) - return [(idx, cmap(norm(idx))) for idx in range(start, stop)] + norm = matplotlib.colors.Normalize(vmin=0, vmax=len(param_range)-1) + return [(idx, cmap(norm(idx))) for idx, _ in enumerate(param_range)] - def save(self, savepath, **kwargs): + def save(self, filename, **kwargs): + if self.density_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) - elif self.frequency_axis: - if self.magnetic_field_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) - else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 )) + if self.scan_parameter_steps < 5: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + else: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.89,0.97)) + + - self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", "Title": "Neutrino mass sensitivity", "Subject":"90% CL upper limit on neutrino mass assuming true mass is zero." } #"Keywords": keywords} - if savepath is not None: - self.fig.savefig(savepath.replace(".pdf", ".png"), dpi=300, metadata=metadata) - self.fig.savefig(savepath.replace(".png", ".pdf"), bbox_inches="tight", metadata=metadata) + + self.fig.tight_layout() + self.fig.savefig(os.path.join(self.plot_path, filename), bbox_inches="tight", metadata=metadata) + self.fig.savefig(os.path.join(self.plot_path, filename.replace(".pdf", ".png")), bbox_inches="tight", metadata=metadata) - if self.make_key_parameter_plots: - self.kp_fig.savefig('key_parameters.pdf') diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index 41c7ae70..63796586 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -11,7 +11,9 @@ from mermithid.processors.Sensitivity import SensitivityParameterScanProcessor import numpy as np -from numericalunits import eV + +# import all the scanned parameter units +from numericalunits import eV, K, mK, T # whatever you need deg = np.pi/180 @@ -22,7 +24,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_density_curve.pdf", + "plot_path": ".", # optional "figsize": (7.0,6), "track_length_axis": True, @@ -33,22 +35,19 @@ "y_limits": [2e-2, 4], "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase IV (0.04 eV)": 0.04}, "scan_parameter_name": "MagneticField.sigmae_r", "scan_parameter_range": [0.1, 5], - "scan_parameter_steps": 3, + "scan_parameter_steps": 4, "scan_parameter_unit": eV, - "plot_sensitivity_scan_on_log_scale": True, - #"sigmae_theta_r": 0.159, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, + "plot_sensitivity_scan_on_log_scale": False, "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False } -#sens_curve = SensitivityParameterScanProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") +sens_scan.Configure(sens_config_dict) +sens_scan.Run() # Configuration for Sensitivity vs. density plot @@ -56,7 +55,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_density_curve.pdf", + "plot_path": ".", # optional "figsize": (7.0,6), "track_length_axis": True, @@ -67,24 +66,20 @@ "y_limits": [2e-2, 4], "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase IV (0.04 eV)": 0.04}, "scan_parameter_name": "FrequencyExtraction.minimum_angle_in_bandwidth", "scan_parameter_range": [83, 89], - "scan_parameter_steps": 3, + "scan_parameter_steps": 10, "scan_parameter_unit": deg, "plot_sensitivity_scan_on_log_scale": False, - #"sigmae_theta_r": 0.159, - "lower_label_y_position": 0.17, - "upper_label_y_position": 0.7, - "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False } -sens_curve = SensitivityParameterScanProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() - +#sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") +#sens_scan.Configure(sens_config_dict) +#sens_scan.Run() +print(sens_scan.results) From 8d3178039d8fa49a2eb584ad53e1b2f1c8633178 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 21 Mar 2024 16:54:29 -0700 Subject: [PATCH 118/262] fixed indentation --- .../Sensitivity/SensitivityParameterScanProcessor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 9547937a..12718698 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -42,9 +42,9 @@ def return_var_name(variable): - for name in globals(): - if eval(name) == variable: - return name + for name in globals(): + if eval(name) == variable: + return name From af773846f13f752231b41015c50cbbeeb68ddf24 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 3 Apr 2024 12:23:33 -0700 Subject: [PATCH 119/262] got example l_over_d scan to work --- .../SensitivityParameterScanProcessor.py | 6 +-- test_analysis/Sensitivity_parameter_scan.py | 37 ++++++++++++++++--- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 12718698..14cd824d 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -170,8 +170,8 @@ def InternalRun(self): #read_back = getattr(self.sens_main, self.scan_parameter_name) logger.info(f"Setting {self.scan_parameter_name} to {parameter_value/self.scan_parameter_unit} and reading back: {read_back/ self.scan_parameter_unit}") - - logger.info("Calculating cavity experiment") + logger.info("Calculating cavity experiment radius, volume, effective volume, power") + self.sens_main.CavityRadius() self.sens_main.CavityVolume() self.sens_main.EffectiveVolume() self.sens_main.CavityPower() @@ -251,7 +251,7 @@ def InternalRun(self): # plot and print best limits self.results = {"scan_parameter": self.scan_parameter_name, "scan parameter_unit": self.scan_parameter_unit_string, "scan_parameter_values": self.scan_parameter_values, "optimum_limits_eV": np.array(self.optimum_limits)/eV, - "optimum_densities_/m3": np.array(self.optimum_rhos)*(m**3)} + "optimum_densities/m3": np.array(self.optimum_rhos)*(m**3)} logger.info("Scan parameter: {}".format(self.scan_parameter_name)) logger.info("Tested parameter values: {}".format(self.scan_parameter_values/self.scan_parameter_unit)) diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index 63796586..e725461b 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -20,7 +20,6 @@ # Configuration for Sensitivity vs. density plot -# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", @@ -45,13 +44,12 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False } -sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") -sens_scan.Configure(sens_config_dict) -sens_scan.Run() +#sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") +#sens_scan.Configure(sens_config_dict) +#sens_scan.Run() # Configuration for Sensitivity vs. density plot -# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", @@ -79,6 +77,35 @@ #sens_scan.Configure(sens_config_dict) #sens_scan.Run() + +# Configuration for Sensitivity vs. density plot +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", + "plot_path": ".", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + #"density_range": [1e8, 1e12], + "goals": {"Phase IV (0.04 eV)": 0.04}, + "scan_parameter_name": "Experiment.l_over_d", + "scan_parameter_range": [5, 8], + "scan_parameter_steps": 3, + "scan_parameter_unit": 1, + "plot_sensitivity_scan_on_log_scale": False, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": False + } +sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") +sens_scan.Configure(sens_config_dict) +sens_scan.Run() + print(sens_scan.results) From b5d7dc9970bbdba4086ed651ba0ac87ab31639fb Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Fri, 19 Apr 2024 09:32:48 -0400 Subject: [PATCH 120/262] 1 GHz and 500 MHz LFA comparison --- test_analysis/LFA_Sensitivity.py | 37 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index bbdc262b..321830be 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -15,7 +15,7 @@ # Configuration for CCA Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LoverDof5_LFA_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/config_LFA_Experiment_500MHz.cfg", #Config_LFA_Experiment_1GHz.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_density_curve.pdf", # optional @@ -26,7 +26,7 @@ "density_axis": True, "cavity": True, #"y_limits": [2e-2, 20], - "y_limits": [3e-1, 20], + "y_limits": [2e-1, 20], "density_range": [1e13,1e20], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], @@ -53,9 +53,9 @@ "configure_sigma_theta_r": True, "sigmae_theta_r": np.linspace(0.1, 0.5, 10), #in eV, energy broadening from theta and r reconstruction } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { @@ -63,42 +63,43 @@ "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_exposure_curve_1GHz.pdf", # optional - "figsize": (10,6), + "figsize": (8,6), "fontsize": 15, - "legend_location": "upper right", + "legend_location": "upper center", "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, "density_axis": False, "cavity": True, - "add_PhaseII": False, "add_1year_1cav_point_to_last_ref": False, - "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "y_limits": [10e-3, 500], "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], - "main_curve_upper_label": r"LFA", + "main_curve_upper_label": r"LFA (atomic T pilot) - 1 GHz", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", - "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "goals": {"Phase III (0.4 eV)": (0.4**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_label": [r"Phase IV 150 MHz scenario"], + "comparison_curve_label": [r"LFA (atomic T pilot) - 500 MHz", r"Phase IV scenario - 150 MHz"], #"comparison_curve_colors": ["blue", "darkred", "red"], - "comparison_curve_colors": ["red"], + "comparison_curve_colors": ["darkred", "red"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.2e-10, #2e12 #0.0002 - "goals_y_rel_position": 0.4 + "goals_y_rel_position": 0.4, + "add_PhaseII": True, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg" } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() From 266a832652743763e2b1af7e62a9579b41ada120 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Fri, 19 Apr 2024 13:53:36 -0400 Subject: [PATCH 121/262] Version with reduced efficiency for lower L/D --- test_analysis/LFA_Sensitivity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index 321830be..d8332c7e 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -15,7 +15,7 @@ # Configuration for CCA Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/config_LFA_Experiment_500MHz.cfg", #Config_LFA_Experiment_1GHz.cfg", + "config_file_path": "/termite/sensitivity_config_files/config_LFA_Experiment_1GHz.cfg", #Config_LFA_Experiment_1GHz.cfg", #"config_file_path": "/host_repos/sensitivity_branches/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_density_curve.pdf", # optional From 4854fdbfbf646a865a444d7d5fcc28b70fd9e337 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Sun, 21 Apr 2024 09:16:27 -0700 Subject: [PATCH 122/262] removed defaulting to exposure axis. now there is no default. --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index fb3ce3a8..860108c4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -115,9 +115,10 @@ def InternalConfigure(self, params): elif self.frequency_axis: self.add_sens_line = self.add_frequency_sens_line logger.info("Doing frequency lines") - else: + elif self.exposure_axis: self.add_sens_line = self.add_exposure_sens_line logger.info("Doing exposure lines") + else: raise ValueError("No axis specified") # goals @@ -645,7 +646,7 @@ def add_Phase_II_exposure_sens_line(self, sens): sens.sensitivity() logger.info("Phase II sensitivity for exposure {} calculated: {}".format(standard_exposure, sens.sensitivity()/eV**2)) - + # Phase II experimental results phaseIIsens = 9822 phaseIIsense_error = 1520 exposure_error = np.sqrt((standard_exposure*0.008)**2 + (standard_exposure*0.09)**2) From 2ba2ca0812ae2a67d81e857bbdcafefd0dfdff58 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 2 May 2024 12:34:22 -0700 Subject: [PATCH 123/262] re-organized print_SNRs function. removed incomplete setting of number density. instead it uses the number density that is already set. --- mermithid/misc/SensitivityCavityFormulas.py | 37 ++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index efba87d9..0fa5d20a 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -538,24 +538,37 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): return tau_snr - def print_SNRs(self, rho_opt): - tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) - logger.info("tau_SNR: {}s".format(tau_snr/s)) + def print_SNRs(self, rho=None): + logger.info("SNR parameters:") + if rho != None: + logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") + + track_duration = self.time_window + tau_snr = self.calculate_tau_snr(track_duration, sideband_power_fraction=1) + + eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) - SNR_1eV = 1/eV_bandwidth/2./tau_snr - track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) - SNR_track_duration = track_duration/2./tau_snr - SNR_1ms = 0.001*s/2./tau_snr - logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) - logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) + SNR_1eV = 1/eV_bandwidth/tau_snr + SNR_track_duration = track_duration/tau_snr + SNR_1ms = 0.001*s/tau_snr + + logger.info("Number density: {} m^-3".format(self.Experiment.number_density*m**3)) logger.info("Track duration: {}ms".format(track_duration/ms)) + logger.info("tau_SNR: {}s".format(tau_snr/s)) logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) - logger.info("SNR for track duration: {}".format(SNR_track_duration)) - logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + logger.info("Received power: {}W".format(self.received_power/W)) - logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) logger.info("Noise temperature: {}K".format(self.noise_temp/K)) + logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) + logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) + logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) + logger.info("SNR for track duration: {}".format(SNR_track_duration)) + logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + + logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + + return self.noise_temp, SNR_1eV, track_duration def syst_frequency_extraction(self): From 020cbc26fc04ddb886e48e70416e8c5799420547 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Thu, 2 May 2024 13:31:00 -0700 Subject: [PATCH 124/262] reorganized mermthid new folders sensitivity and cavity that contain function definitions used by the sensitivity processors. created new CRESFunctions_numericalunits.py and Constants_numericalunits.py to separate from ConversionFunctions.py and Constants.py that do not use numerical units. --- .../{misc => cavity}/HannekeFunctions.py | 0 mermithid/cavity/__init__.py | 6 ++ .../misc/CRESFunctions_numericalunits.py | 51 ++++++++++++ mermithid/misc/Constants_numericalunits.py | 45 ++++++++++ mermithid/misc/ConversionFunctions.py | 2 + mermithid/misc/__init__.py | 5 +- .../AnalyticSensitivityEstimation.py | 2 +- .../CavitySensitivityCurveProcessor.py | 18 ++-- .../ConstantSensitivityParameterPlots.py | 2 +- .../Sensitivity/SensitivityCurveProcessor.py | 2 +- .../SensitivityParameterScanProcessor.py | 7 +- mermithid/processors/misc/FrequencyShifter.py | 2 +- .../SensitivityCavityFormulas.py | 77 +----------------- .../SensitivityFormulas.py | 0 mermithid/sensitivity/__init__.py | 7 ++ tests/stat_and_syst_vs_density.pdf | Bin 17579 -> 0 bytes 16 files changed, 128 insertions(+), 98 deletions(-) rename mermithid/{misc => cavity}/HannekeFunctions.py (100%) create mode 100644 mermithid/cavity/__init__.py create mode 100644 mermithid/misc/CRESFunctions_numericalunits.py create mode 100644 mermithid/misc/Constants_numericalunits.py rename mermithid/{misc => sensitivity}/SensitivityCavityFormulas.py (93%) rename mermithid/{misc => sensitivity}/SensitivityFormulas.py (100%) create mode 100644 mermithid/sensitivity/__init__.py delete mode 100644 tests/stat_and_syst_vs_density.pdf diff --git a/mermithid/misc/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py similarity index 100% rename from mermithid/misc/HannekeFunctions.py rename to mermithid/cavity/HannekeFunctions.py diff --git a/mermithid/cavity/__init__.py b/mermithid/cavity/__init__.py new file mode 100644 index 00000000..78b46a30 --- /dev/null +++ b/mermithid/cavity/__init__.py @@ -0,0 +1,6 @@ +''' +''' + +from __future__ import absolute_import + +from . import HannekeFunctions \ No newline at end of file diff --git a/mermithid/misc/CRESFunctions_numericalunits.py b/mermithid/misc/CRESFunctions_numericalunits.py new file mode 100644 index 00000000..825cfd88 --- /dev/null +++ b/mermithid/misc/CRESFunctions_numericalunits.py @@ -0,0 +1,51 @@ +''' +Miscellaneous functions for CRES conversions +Author: C. Claessens +Date:4/19/2020 +''' + +from __future__ import absolute_import + +import numpy as np + +from morpho.utilities import morphologging +logger = morphologging.getLogger(__name__) + +from mermithid.misc.Constants_numericalunits import * + + +def gamma(kin_energy): + return kin_energy/(me*c0**2) + 1 + +def beta(kin_energy): + # electron speed at kin_energy + return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) + +def frequency(kin_energy, magnetic_field): + # cyclotron frequency + return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field + +def wavelength(kin_energy, magnetic_field): + return c0/frequency(kin_energy, magnetic_field) + +def kin_energy(freq, magnetic_field): + return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) + +def rad_power(kin_energy, pitch, magnetic_field): + # electron radiation power + f = frequency(kin_energy, magnetic_field) + b = beta(kin_energy) + Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) + return Pe + +def track_length(rho, kin_energy=None, molecular=True): + if kin_energy is None: + kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic + crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic + return 1 / (rho * crosssect * beta(kin_energy) * c0) + +def sin2theta_sq_to_Ue4_sq(sin2theta_sq): + return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) + +def Ue4_sq_to_sin2theta_sq(Ue4_sq): + return 4*Ue4_sq*(1-Ue4_sq) \ No newline at end of file diff --git a/mermithid/misc/Constants_numericalunits.py b/mermithid/misc/Constants_numericalunits.py new file mode 100644 index 00000000..08d092b0 --- /dev/null +++ b/mermithid/misc/Constants_numericalunits.py @@ -0,0 +1,45 @@ +''' +Some constants useful for various things... +''' + +import numpy as np + +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day, s, ms +from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck + + +T0 = -273.15*K + +tritium_livetime = 5.605e8*s +tritium_mass_atomic = 3.016* amu *c0**2 +tritium_electron_crosssection_atomic = 9.e-23*m**2 #Hamish extrapolated to 18.6keV using Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 +tritium_endpoint_atomic = 18563.251*eV +last_1ev_fraction_atomic = 2.067914e-13/eV**3 + +tritium_mass_molecular = 6.032099 * amu *c0**2 +tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 #[Inelastic from Aseev (2000) for T2] + [Elastic from Liu (1987) for H2, extrapolated by Elise to 18.6keV] +tritium_endpoint_molecular = 18574.01*eV +last_1ev_fraction_molecular = 1.67364e-13/eV**3 + +ground_state_width = 0.436 * eV +#ground_state_width_uncertainty = 0.001*0.436*eV + +gyro_mag_ratio_proton = 42.577*MHz/T + +# units that do not show up in numericalunits +# missing pre-factors +fW = W*1e-15 + +# unitless units, relative fractions +pc = 0.01 +ppm = 1e-6 +ppb = 1e-9 +ppt = 1e-12 +ppq = 1e-15 + +# radian and degree which are also not really units +rad = 1 +deg = np.pi/180 \ No newline at end of file diff --git a/mermithid/misc/ConversionFunctions.py b/mermithid/misc/ConversionFunctions.py index c1a59a85..88590af6 100644 --- a/mermithid/misc/ConversionFunctions.py +++ b/mermithid/misc/ConversionFunctions.py @@ -33,3 +33,5 @@ def Energy(F, B=1): else: gamma = (e()*B)/(2.0*np.pi*emass_kg) * 1/(F) return (gamma-1)*m_electron() + + diff --git a/mermithid/misc/__init__.py b/mermithid/misc/__init__.py index 0951b7ba..fa5aaa83 100644 --- a/mermithid/misc/__init__.py +++ b/mermithid/misc/__init__.py @@ -8,6 +8,5 @@ from . import TritiumFormFactor from . import FakeTritiumDataFunctions from . import ConversionFunctions -from . import SensitivityFormulas -from . import SensitivityCavityFormulas -from . import HannekeFunctions \ No newline at end of file +from . import CRESFunctions_numericalunits +from . import Constants_numericalunits \ No newline at end of file diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py index 650ff09d..c82b3b51 100644 --- a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -22,7 +22,7 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor -from mermithid.misc.SensitivityFormulas import Sensitivity +from mermithid.sensitivity.SensitivityFormulas import Sensitivity logger = morphologging.getLogger(__name__) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 860108c4..6695560a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -27,8 +27,7 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor -from mermithid.misc.SensitivityFormulas import Sensitivity -from mermithid.misc.SensitivityCavityFormulas import CavitySensitivity +from mermithid.sensitivity.SensitivityCavityFormulas import CavitySensitivity logger = morphologging.getLogger(__name__) @@ -129,22 +128,15 @@ def InternalConfigure(self, params): if self.add_PhaseII: self.sens_PhaseII = CavitySensitivity(self.PhaseII_path) - self.cavity = reader.read_param(params, 'cavity', True) - if self.cavity: - self.sens_main = CavitySensitivity(self.config_file_path) - else: - self.sens_main = Sensitivity(self.config_file_path) + self.sens_main = CavitySensitivity(self.config_file_path) self.sens_main_is_atomic = self.sens_main.Experiment.atomic if self.comparison_curve: ref = [] - if self.cavity: - for file in self.comparison_config_file_path: - ref.append(CavitySensitivity(file)) - else: - for file in self.comparison_config_file_path: - ref.append(CavitySensitivity(file)) + for file in self.comparison_config_file_path: + ref.append(CavitySensitivity(file)) + self.sens_ref = ref is_atomic = [] for i in range(len(self.sens_ref)): diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index 514de430..18aab05d 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -26,7 +26,7 @@ from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor from mermithid.processors.Sensitivity import AnalyticSensitivityEstimation -from mermithid.misc.SensitivityFormulas import Sensitivity +from mermithid.sensitivity.SensitivityFormulas import Sensitivity logger = morphologging.getLogger(__name__) diff --git a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py index 27ab452e..ddb2f129 100644 --- a/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityCurveProcessor.py @@ -27,7 +27,7 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor -from mermithid.misc.SensitivityFormulas import Sensitivity +from mermithid.sensitivity.SensitivityFormulas import Sensitivity logger = morphologging.getLogger(__name__) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 14cd824d..de0ae5ac 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -29,8 +29,8 @@ # morpho imports from morpho.utilities import morphologging, reader from morpho.processors import BaseProcessor -from mermithid.misc.SensitivityFormulas import Sensitivity -from mermithid.misc.SensitivityCavityFormulas import CavitySensitivity +from mermithid.sensitivity.SensitivityFormulas import Sensitivity +from mermithid.sensitivity.SensitivityCavityFormulas import CavitySensitivity logger = morphologging.getLogger(__name__) @@ -183,7 +183,6 @@ def InternalRun(self): opt = np.argmin(limit) rho_opt = self.rhos[opt] self.optimum_rhos.append(rho_opt) - self.sens_main.Experiment.number_density = rho_opt # add main curve logger.info("Drawing main curve") @@ -231,7 +230,7 @@ def InternalRun(self): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) - self.sens_main.print_SNRs(rho_opt) + self.sens_main.print_SNRs() logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* diff --git a/mermithid/processors/misc/FrequencyShifter.py b/mermithid/processors/misc/FrequencyShifter.py index dfc900aa..d65d46ef 100644 --- a/mermithid/processors/misc/FrequencyShifter.py +++ b/mermithid/processors/misc/FrequencyShifter.py @@ -32,7 +32,7 @@ def InternalRun(self): self.results = {} return True if self.frequency_shift == 0: - looger.warning("Zero frequency shift!") + logger.warning("Zero frequency shift!") self.results = self.frequencies return True for frequency in self.frequencies: diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py similarity index 93% rename from mermithid/misc/SensitivityCavityFormulas.py rename to mermithid/sensitivity/SensitivityCavityFormulas.py index 0fa5d20a..945ebbaf 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -9,48 +9,11 @@ import numpy as np import configparser from numpy import pi -from mermithid.misc.HannekeFunctions import * -# Numericalunits is a package to handle units and some natural constants -# natural constants -from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ -from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W -from numericalunits import hour, year, day, s, ms -from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck +from mermithid.misc.Constants_numericalunits import * +from mermithid.misc.CRESFunctions_numericalunits import * +from mermithid.cavity.HannekeFunctions import * -T0 = -273.15*K - -tritium_livetime = 5.605e8*s -tritium_mass_atomic = 3.016* amu *c0**2 -tritium_electron_crosssection_atomic = 9.e-23*m**2 #Hamish extrapolated to 18.6keV using Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 -tritium_endpoint_atomic = 18563.251*eV -last_1ev_fraction_atomic = 2.067914e-13/eV**3 - -tritium_mass_molecular = 6.032099 * amu *c0**2 -tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 #[Inelastic from Aseev (2000) for T2] + [Elastic from Liu (1987) for H2, extrapolated by Elise to 18.6keV] -tritium_endpoint_molecular = 18574.01*eV -last_1ev_fraction_molecular = 1.67364e-13/eV**3 - -ground_state_width = 0.436 * eV -#ground_state_width_uncertainty = 0.001*0.436*eV - -gyro_mag_ratio_proton = 42.577*MHz/T - -# units that do not show up in numericalunits -# missing pre-factors -fW = W*1e-15 - -# unitless units, relative fractions -pc = 0.01 -ppm = 1e-6 -ppb = 1e-9 -ppt = 1e-12 -ppq = 1e-15 - -# radian and degree which are also not really units -rad = 1 -deg = np.pi/180 try: @@ -70,41 +33,7 @@ def __getattribute__(self, item): ############################################################################## # CRES functions -def gamma(kin_energy): - return kin_energy/(me*c0**2) + 1 - -def beta(kin_energy): - # electron speed at kin_energy - return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) - -def frequency(kin_energy, magnetic_field): - # cyclotron frequency - return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field - -def wavelength(kin_energy, magnetic_field): - return c0/frequency(kin_energy, magnetic_field) - -def kin_energy(freq, magnetic_field): - return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) - -def rad_power(kin_energy, pitch, magnetic_field): - # electron radiation power - f = frequency(kin_energy, magnetic_field) - b = beta(kin_energy) - Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) - return Pe - -def track_length(rho, kin_energy=None, molecular=True): - if kin_energy is None: - kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic - crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic - return 1 / (rho * crosssect * beta(kin_energy) * c0) - -def sin2theta_sq_to_Ue4_sq(sin2theta_sq): - return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) -def Ue4_sq_to_sin2theta_sq(Ue4_sq): - return 4*Ue4_sq*(1-Ue4_sq) # Wouters functinos def db_to_pwr_ratio(q_db): diff --git a/mermithid/misc/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py similarity index 100% rename from mermithid/misc/SensitivityFormulas.py rename to mermithid/sensitivity/SensitivityFormulas.py diff --git a/mermithid/sensitivity/__init__.py b/mermithid/sensitivity/__init__.py new file mode 100644 index 00000000..f5cebfc9 --- /dev/null +++ b/mermithid/sensitivity/__init__.py @@ -0,0 +1,7 @@ +''' +''' + +from __future__ import absolute_import + +from . import SensitivityFormulas +from . import SensitivityCavityFormulas \ No newline at end of file diff --git a/tests/stat_and_syst_vs_density.pdf b/tests/stat_and_syst_vs_density.pdf deleted file mode 100644 index c7c725750f048afdca09b469cd4cc2eec352f0f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17579 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 From 1e031c4da224821b6621dcffc1aa8379e7a0541a Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 3 May 2024 13:57:44 -0700 Subject: [PATCH 125/262] reducing code dublication --- .../sensitivity/SensitivityCavityFormulas.py | 388 +++--------------- mermithid/sensitivity/SensitivityFormulas.py | 103 ++--- 2 files changed, 74 insertions(+), 417 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 945ebbaf..068149c0 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -13,6 +13,7 @@ from mermithid.misc.Constants_numericalunits import * from mermithid.misc.CRESFunctions_numericalunits import * from mermithid.cavity.HannekeFunctions import * +from mermithid.sensitivity.SensitivityFormulas import * @@ -22,17 +23,6 @@ except: print("Run without morpho!") -class NameSpace(object): - def __init__(self, iteritems): - if type(iteritems) == dict: - iteritems = iteritems.items() - for k, v in iteritems: - setattr(self, k.lower(), v) - def __getattribute__(self, item): - return object.__getattribute__(self, item.lower()) - -############################################################################## -# CRES functions # Wouters functinos @@ -103,7 +93,7 @@ def t_effective(t_physical, cyclotron_frequency): return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) ############################################################################### -class CavitySensitivity(object): +class CavitySensitivity(Sensitivity): """ Documentation: * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 @@ -112,42 +102,9 @@ class CavitySensitivity(object): * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ def __init__(self, config_path): - self.cfg = configparser.ConfigParser() - with open(config_path, 'r') as configfile: - self.cfg.read_file(configfile) - - # display configuration - try: - logger.info("Config file content:") - for sect in self.cfg.sections(): - logger.info(' Section: {}'.format(sect)) - for k,v in self.cfg.items(sect): - logger.info(' {} = {}'.format(k,v)) - except: - pass - - self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - - self.tau_tritium = tritium_livetime - if self.Experiment.atomic: - self.T_mass = tritium_mass_atomic - self.Te_crosssection = tritium_electron_crosssection_atomic - self.T_endpoint = tritium_endpoint_atomic - self.last_1ev_fraction = last_1ev_fraction_atomic - else: - self.T_mass = tritium_mass_molecular - self.Te_crosssection = tritium_electron_crosssection_molecular - self.T_endpoint = tritium_endpoint_molecular - self.last_1ev_fraction = last_1ev_fraction_molecular - - - self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) - self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) - self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) - self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) - self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) - self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + Sensitivity.__init__(self, config_path) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + self.CRLB_constant = 12 #self.CRLB_constant = 90 @@ -189,6 +146,10 @@ def EffectiveVolume(self): self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor + + # for parent SignalRate function + self.Experiment.v_eff = self.effective_volume + return self.effective_volume def PitchDependentTrappingEfficiency(self): @@ -200,7 +161,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, @@ -233,197 +194,11 @@ def CavityLoadedQ(self): return self.loaded_q # SENSITIVITY - def SignalRate(self): - """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - #self.EffectiveVolume() - signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium - if not self.Experiment.atomic: - if hasattr(self.Experiment, 'gas_fractions'): - avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) - signal_rate *= avg_n_T_atoms - else: - signal_rate *= 2 - if hasattr(self.Experiment, 'active_gas_fraction'): - signal_rate *= self.Experiment.active_gas_fraction - return signal_rate - - def BackgroundRate(self): - """background rate, can be calculated from multiple components. - Assumes that background rate is constant over considered energy / frequency range.""" - return self.Experiment.background_rate_per_eV - - def SignalEvents(self): - """Number of signal events.""" - return self.SignalRate()*self.Experiment.LiveTime*self.DeltaEWidth()**3 - - def BackgroundEvents(self): - """Number of background events.""" - return self.BackgroundRate()*self.Experiment.LiveTime*self.DeltaEWidth() - - def DeltaEWidth(self): - """optimal energy bin width""" - labels, sigmas, deltas = self.get_systematics() - return np.sqrt(self.BackgroundRate()/self.SignalRate() - + 8*np.log(2)*(np.sum(sigmas**2))) - - def StatSens(self): - """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" - sig_rate = self.SignalRate() - DeltaE = self.DeltaEWidth() - sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) - return sens - - def SystSens(self): - """Pure systematic componenet to sensitivity""" - labels, sigmas, deltas = self.get_systematics() - sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) - return sens - - def sensitivity(self, **kwargs): - """Combined statisical and systematic uncertainty. - Using kwargs settings in namespaces can be changed. - Example how to change number density which lives in namespace Experiment: - self.sensitivity(Experiment={"number_density": rho}) - """ - for sect, options in kwargs.items(): - for opt, val in options.items(): - self.__dict__[sect].__dict__[opt] = val - - StatSens = self.StatSens() - SystSens = self.SystSens() - - # Standard deviation on a measurement of m_beta**2 assuming a mass of zero - sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) - return sigma_m_beta_2 - - def CL90(self, **kwargs): - """ Gives 90% CL upper limit on neutrino mass.""" - # 90% of gaussian are contained in +-1.64 sigma region - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - - def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) - - # PHYSICS Functions - - def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas_fractions): - """ - Given gas composition info (H2 vs. other gases, and how much of each H2-type isotopolog), returns an average number of tritium atoms per gas particle. - - Inputs: - - gas_fractions: dict of composition fractions of each gas (different from scatter fractions!); all H2 isotopologs are combined under key 'H2' - - H2_type_gas_fractions: dict with fraction of each isotopolog, out of total amount of H2 - """ - H2_iso_avg_num = 0 - for (key, val) in H2_type_gas_fractions.items(): - if key=='T2': - H2_iso_avg_num += 2*val - elif key=='HT' or key=='DT': - H2_iso_avg_num += val - elif key=='H2' or key=='HD' or key=='D2': - pass - logger.info("Activive fraction: {}".format(gas_fractions['H2']*H2_iso_avg_num)) - return gas_fractions['H2']*H2_iso_avg_num - - def BToKeErr(self, BErr, B): - return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) - - def KeToBerr(self, KeErr, B): - return KeErr/e*(2*np.pi*frequency(self.T_endpoint, B)/c0**2) - - def track_length(self, rho): - return track_length(rho, self.T_endpoint, not self.Experiment.atomic) + # see parent class in SensitivityFormulas.py + # SYSTEMATICS - - def get_systematics(self): - """ Returns list of energy broadenings (sigmas) and - uncertainties on these energy broadenings (deltas) - for all considered systematics. We need to make sure - that we do not include effects twice or miss any - important effect. - - Returns: - * list of labels - * list of energy broadenings - * list of energy broadening uncertainties - """ - - # Different types of uncertainty contributions - sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() - sigma_f, delta_sigma_f = self.syst_frequency_extraction() - sigma_B, delta_sigma_B = self.syst_magnetic_field() - sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() - sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() - - labels = ["Thermal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] - sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] - deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] - - if not self.Experiment.atomic: - labels.append("Molecular final state") - sigmas.append(ground_state_width) - deltas.append(self.FinalStates.ground_state_width_uncertainty_fraction*ground_state_width) - - return np.array(labels), np.array(sigmas), np.array(deltas) - - def print_statistics(self): - print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") - - def print_systematics(self): - labels, sigmas, deltas = self.get_systematics() - - print() - sigma_squared = 0 - for label, sigma, delta in zip(labels, sigmas, deltas): - print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") - sigma_squared += sigma**2 - sigma_total = np.sqrt(sigma_squared) - print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) - try: - print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") - except AttributeError: - pass - print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") - print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") - - def syst_doppler_broadening(self): - # estimated standard deviation of Doppler broadening distribution from - # translational motion of tritium atoms / molecules - # Predicted uncertainty on standard deviation, based on expected precision - # of temperature knowledge - if self.DopplerBroadening.UseFixedValue: - sigma = self.DopplerBroadening.Default_Systematic_Smearing - delta = self.DopplerBroadening.Default_Systematic_Uncertainty - return sigma, delta - - # thermal doppler broardening - gasTemp = self.DopplerBroadening.gas_temperature - mass_T = self.T_mass - endpoint = self.T_endpoint - - # these factors are mainly neglidible in the recoil equation below - E_rec = 3.409 * eV # maximal value # same for molecular tritium? - mbeta = 0*eV # term neglidible - betanu = 1 # neutrinos are fast - # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu - # => expectation value of cosThetanu = 0.014 - cosThetaenu = 0.014 - - Ke = endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke - Emax = endpoint + me*c0**2 - Ee = endpoint + me*c0**2 - p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) - sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) - - if self.Experiment.atomic == True: - delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) - else: - delta_trans = sigma_trans*self.DopplerBroadening.fraction_uncertainty_on_doppler_broadening - return sigma_trans, delta_trans + # Generic systematics are implemented in the parent class in SensitivityFormulas.py def calculate_tau_snr(self, time_window, sideband_power_fraction=1): @@ -465,39 +240,6 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # end of Wouter's calculation return tau_snr - - - def print_SNRs(self, rho=None): - logger.info("SNR parameters:") - if rho != None: - logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") - - track_duration = self.time_window - tau_snr = self.calculate_tau_snr(track_duration, sideband_power_fraction=1) - - - eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) - SNR_1eV = 1/eV_bandwidth/tau_snr - SNR_track_duration = track_duration/tau_snr - SNR_1ms = 0.001*s/tau_snr - - logger.info("Number density: {} m^-3".format(self.Experiment.number_density*m**3)) - logger.info("Track duration: {}ms".format(track_duration/ms)) - logger.info("tau_SNR: {}s".format(tau_snr/s)) - logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) - - logger.info("Received power: {}W".format(self.received_power/W)) - logger.info("Noise temperature: {}K".format(self.noise_temp/K)) - logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) - logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) - logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) - logger.info("SNR for track duration: {}".format(SNR_track_duration)) - logger.info("SNR for 1 ms: {}".format(SNR_1ms)) - - - logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) - - return self.noise_temp, SNR_1eV, track_duration def syst_frequency_extraction(self): @@ -600,7 +342,9 @@ def syst_frequency_extraction(self): raise NotImplementedError("Unvertainty on CRLB for cavity noise calculation is not implemented.") def syst_magnetic_field(self): - + """ + Magnetic field uncertanty is in principle generic but its impact on efficiency depends on reconstruction and therefore on detector technology. + """ # magnetic field uncertainties can be decomposed in several part # * true magnetic field inhomogeneity # (would be there also without a trap) @@ -627,71 +371,37 @@ def syst_magnetic_field(self): return sigma, frac_uncertainty*sigma else: return 0, 0 - """ - BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability - delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution - - BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field - delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? - - Delta_t_since_calib = self.MagneticField.time_since_calibration - shiftBdot = self.MagneticField.shift_Bdot - smearBdot = self.MagneticField.smear_Bdot - delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot - delta_smearBdot = self.MagneticField.uncertainty_smearBdot - BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) - delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) - - # position uncertainty is linear in wavelength - # position uncertainty is nearly constant w.r.t. radial position - # based on https://3.basecamp.com/3700981/buckets/3107037/uploads/3442593126 - rRecoErr = self.MagneticField.rRecoErr - delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr - - rRecoPhiErr = self.MagneticField.rRecoPhiErr - delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr - - rProbeErr = self.MagneticField.rProbeErr - delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr - - rProbePhiErr = self.MagneticField.rProbePhiErr - delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr - - Berr = np.sqrt(BMapErr**2 + - BFlatErr**2 + - BdotErr**2 + - rRecoErr**2 + - rRecoPhiErr**2 + - rProbeErr**2 + - rProbePhiErr**2) - - delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + - BFlatErr**2 * delta_BFlatErr**2 + - BdotErr**2 * delta_BdotErr**2 + - rRecoErr**2 * delta_rRecoErr**2 + - rRecoPhiErr**2 * delta_rRecoPhiErr**2 + - rProbeErr**2 * delta_rProbeErr**2 + - rProbePhiErr**2 * delta_rProbePhiErr**2) - - return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) - """ - - def syst_missing_tracks(self): - # this systematic should describe the energy broadening due to the line shape. - # Line shape is caused because you miss the first n tracks but then detect the n+1 - # track and you assume that this is the start frequency. - # This depends on the gas composition, density and cross-section. - if self.MissingTracks.UseFixedValue: - sigma = self.MissingTracks.Default_Systematic_Smearing - delta = self.MissingTracks.Default_Systematic_Uncertainty - return sigma, delta - else: - raise NotImplementedError("Missing track systematic is not implemented.") - - def syst_plasma_effects(self): - if self.PlasmaEffects.UseFixedValue: - sigma = self.PlasmaEffects.Default_Systematic_Smearing - delta = self.PlasmaEffects.Default_Systematic_Uncertainty - return sigma, delta - else: - raise NotImplementedError("Plasma effect sysstematic is not implemented.") + + + # PRINTS + def print_SNRs(self, rho=None): + logger.info("SNR parameters:") + if rho != None: + logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") + + track_duration = self.time_window + tau_snr = self.calculate_tau_snr(track_duration, sideband_power_fraction=1) + + + eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) + SNR_1eV = 1/eV_bandwidth/tau_snr + SNR_track_duration = track_duration/tau_snr + SNR_1ms = 0.001*s/tau_snr + + logger.info("Number density: {} m^-3".format(self.Experiment.number_density*m**3)) + logger.info("Track duration: {}ms".format(track_duration/ms)) + logger.info("tau_SNR: {}s".format(tau_snr/s)) + logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) + + logger.info("Received power: {}W".format(self.received_power/W)) + logger.info("Noise temperature: {}K".format(self.noise_temp/K)) + logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) + logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) + logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) + logger.info("SNR for track duration: {}".format(SNR_track_duration)) + logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + + + logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + + return self.noise_temp, SNR_1eV, track_duration diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 1be88932..34dbbc16 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -10,47 +10,8 @@ import configparser from numpy import pi -# Numericalunits is a package to handle units and some natural constants -# natural constants -from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu -from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W -from numericalunits import hour, year, day -from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck - -T0 = -273.15*K - -tritium_livetime = 5.605e8*s -tritium_mass_atomic = 3.016* amu *c0**2 -tritium_electron_crosssection_atomic = 1.1e-22*m**2 -tritium_endpoint_atomic = 18563.251*eV -last_1ev_fraction_atomic = 2.067914e-13/eV**3 - -tritium_mass_molecular = 6.032099 * amu *c0**2 -tritium_electron_crosssection_molecular = 3.487*1e-22*m**2 -tritium_endpoint_molecular = 18573.24*eV -last_1ev_fraction_molecular = 1.67364e-13/eV**3 - -ground_state_width = 0.436 * eV -ground_state_width_uncertainty = 0.01*0.436*eV - -gyro_mag_ratio_proton = 42.577*MHz/T - -# units that do not show up in numericalunits -# missing pre-factors -fW = W*1e-15 - -# unitless units, relative fractions -pc = 0.01 -ppm = 1e-6 -ppb = 1e-9 -ppt = 1e-12 -ppq = 1e-15 - -# radian and degree which are also not really units -rad = 1 -deg = np.pi/180 - +from mermithid.misc.Constants_numericalunits import * +from mermithid.misc.CRESFunctions_numericalunits import * try: from morpho.utilities import morphologging @@ -67,40 +28,6 @@ def __init__(self, iteritems): def __getattribute__(self, item): return object.__getattribute__(self, item.lower()) -############################################################################## -# CRES functions -def gamma(kin_energy): - return kin_energy/(me*c0**2) + 1 - -def beta(kin_energy): - # electron speed at kin_energy - return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) - -def frequency(kin_energy, magnetic_field): - # cyclotron frequency - return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field - -def kin_energy(freq, magnetic_field): - return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) - -def rad_power(kin_energy, pitch, magnetic_field): - # electron radiation power - f = frequency(kin_energy, magnetic_field) - b = beta(kin_energy) - Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) - return Pe - -def track_length(rho, kin_energy=None, molecular=True): - if kin_energy is None: - kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic - crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic - return 1 / (rho * crosssect * beta(kin_energy) * c0) - -def sin2theta_sq_to_Ue4_sq(sin2theta_sq): - return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) - -def Ue4_sq_to_sin2theta_sq(Ue4_sq): - return 4*Ue4_sq*(1-Ue4_sq) ############################################################################### class Sensitivity(object): @@ -146,6 +73,7 @@ def __init__(self, config_path): self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) # SENSITIVITY def SignalRate(self): @@ -157,6 +85,8 @@ def SignalRate(self): signal_rate *= avg_n_T_atoms else: signal_rate *= 2 + if hasattr(self.Experiment, 'active_gas_fraction'): + signal_rate *= self.Experiment.active_gas_fraction return signal_rate def BackgroundRate(self): @@ -275,19 +205,33 @@ def get_systematics(self): if not self.Experiment.atomic: labels.append("Molecular final state") sigmas.append(ground_state_width) - deltas.append(ground_state_width_uncertainty) + if hasattr(self.FinalStates, "ground_state_width_uncertainty_fraction"): + deltas.append(self.FinalStates.ground_state_width_uncertainty_fraction*ground_state_width) + else: + deltas.append(ground_state_width_uncertainty) return np.array(labels), np.array(sigmas), np.array(deltas) def print_statistics(self): - print("Statistical", " "*18, "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") + print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() print() + sigma_squared = 0 for label, sigma, delta in zip(labels, sigmas, deltas): print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") + sigma_squared += sigma**2 + sigma_total = np.sqrt(sigma_squared) + print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) + try: + print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") + except AttributeError: + pass + print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") + print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from @@ -319,7 +263,10 @@ def syst_doppler_broadening(self): p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) - delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + if self.Experiment.atomic == True: + delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) + else: + delta_trans = sigma_trans*self.DopplerBroadening.fraction_uncertainty_on_doppler_broadening return sigma_trans, delta_trans From cc57be172049579632a25f6aac506d70e29bfc2d Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 3 May 2024 14:34:26 -0700 Subject: [PATCH 126/262] replaced pi with np.pi --- mermithid/misc/CRESFunctions_numericalunits.py | 2 +- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 +--- mermithid/sensitivity/SensitivityFormulas.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/mermithid/misc/CRESFunctions_numericalunits.py b/mermithid/misc/CRESFunctions_numericalunits.py index 825cfd88..36b7db4f 100644 --- a/mermithid/misc/CRESFunctions_numericalunits.py +++ b/mermithid/misc/CRESFunctions_numericalunits.py @@ -1,7 +1,7 @@ ''' Miscellaneous functions for CRES conversions Author: C. Claessens -Date:4/19/2020 +Date: 05/02/2024 ''' from __future__ import absolute_import diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 068149c0..24879f84 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -7,8 +7,6 @@ CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np -import configparser -from numpy import pi from mermithid.misc.Constants_numericalunits import * from mermithid.misc.CRESFunctions_numericalunits import * @@ -39,7 +37,7 @@ def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, m # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. #y = (90-max_pitch_angle)/4 - phi_rad = (90-max_pitch_angle)/180*pi + phi_rad = (90-max_pitch_angle)/180*np.pi return 0.16*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) #return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 34dbbc16..79aff434 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -8,7 +8,6 @@ ''' import numpy as np import configparser -from numpy import pi from mermithid.misc.Constants_numericalunits import * from mermithid.misc.CRESFunctions_numericalunits import * @@ -293,7 +292,7 @@ def syst_frequency_extraction(self): Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope # quantum limited noise - sigNoise = np.sqrt((2*pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level + sigNoise = np.sqrt((2*np.pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts From c6ef59a4803029a67f0e19d7d20d78dacb06725b Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 7 May 2024 10:44:45 -0700 Subject: [PATCH 127/262] addressing review comments. also made final state configuration optional. --- mermithid/misc/CRESFunctions_numericalunits.py | 3 +++ mermithid/misc/Constants_numericalunits.py | 3 ++- .../Sensitivity/SensitivityParameterScanProcessor.py | 8 +++----- mermithid/sensitivity/SensitivityFormulas.py | 10 ++++++++-- tests/Sensitivity_test.py | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/mermithid/misc/CRESFunctions_numericalunits.py b/mermithid/misc/CRESFunctions_numericalunits.py index 36b7db4f..19a327b2 100644 --- a/mermithid/misc/CRESFunctions_numericalunits.py +++ b/mermithid/misc/CRESFunctions_numericalunits.py @@ -2,6 +2,9 @@ Miscellaneous functions for CRES conversions Author: C. Claessens Date: 05/02/2024 + +Note: The functions here use the numericalunits package. They are used by the Sensitivity calculations. +For CRES functions not using numericalunits import from ConversionFunctions.py. ''' from __future__ import absolute_import diff --git a/mermithid/misc/Constants_numericalunits.py b/mermithid/misc/Constants_numericalunits.py index 08d092b0..f5e8923f 100644 --- a/mermithid/misc/Constants_numericalunits.py +++ b/mermithid/misc/Constants_numericalunits.py @@ -1,5 +1,6 @@ ''' Some constants useful for various things... +The constants here use the numericalunits package. For constants not using this package import form Constants.py ''' import numpy as np @@ -25,7 +26,7 @@ last_1ev_fraction_molecular = 1.67364e-13/eV**3 ground_state_width = 0.436 * eV -#ground_state_width_uncertainty = 0.001*0.436*eV +ground_state_width_uncertainty = 0.001*0.436*eV gyro_mag_ratio_proton = 42.577*MHz/T diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index de0ae5ac..1bb393bc 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -196,7 +196,7 @@ def InternalRun(self): sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] for n in self.rhos: - self.sens_main.Experiment.number_density = n + self.sens_main.CL90(Experiment={"number_density": n}) labels, sigmas, deltas = self.sens_main.get_systematics() sigma_startf.append(sigmas[1]) stat_on_mbeta2.append(self.sens_main.StatSens()) @@ -214,9 +214,6 @@ def InternalRun(self): - - - logger.info('Experiment info:') # set optimum density back self.sens_main.CL90(Experiment={"number_density": rho_opt}) @@ -368,7 +365,8 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): self.ax.plot(self.rhos*m**3, limits, **kwargs) rho_opt = self.rhos[np.argmin(limits)] - self.sens_main.Experiment.number_density = rho_opt + # set experiment to optimum density + sens.CL90(Experiment={"number_density": rho_opt}) logger.info('Minimum limit at {}: {}'.format(rho_opt*m**3, np.min(limits))) if self.make_key_parameter_plots and plot_key_params: diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 79aff434..2fb7ee07 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -72,7 +72,13 @@ def __init__(self, config_path): self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) - self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) + + if self.cfg.has_section('FinalStates'): + self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) + + if not self.Experiment.atomic and not self.cfg.has_section('FinalStates'): + logger.warning(f"No configuration of ground state width uncertainty. Using default value {ground_state_width_uncertainty/eV} eV") + # SENSITIVITY def SignalRate(self): @@ -204,7 +210,7 @@ def get_systematics(self): if not self.Experiment.atomic: labels.append("Molecular final state") sigmas.append(ground_state_width) - if hasattr(self.FinalStates, "ground_state_width_uncertainty_fraction"): + if self.cfg.has_section('FinalStates') and hasattr(self.FinalStates, "ground_state_width_uncertainty_fraction"): deltas.append(self.FinalStates.ground_state_width_uncertainty_fraction*ground_state_width) else: deltas.append(ground_state_width_uncertainty) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 80df485b..5554f87e 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -81,7 +81,7 @@ def test_SensitivityCurveProcessor(self): "comparison_curve_label": ["Molecular, reaching target", "Atomic, conservative", "Atomic, reaching target"], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, #"comparison_curve_colors": ["red"], "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", From 9bced1d07d2b3aa2091505e6aed8ff62979f420c Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 7 May 2024 11:59:59 -0700 Subject: [PATCH 128/262] Added return statements to cavity formulas print functions to let scan processor store relevant result values. Currently giving very different numbers though. --- mermithid/misc/SensitivityCavityFormulas.py | 17 +++++++ .../SensitivityParameterScanProcessor.py | 44 ++++++++++++++++--- test_analysis/Sensitivity_parameter_scan.py | 41 +++++++++++------ 3 files changed, 84 insertions(+), 18 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index efba87d9..dfd9e7d8 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -271,6 +271,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + #np.random.seed(1) self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, @@ -459,6 +460,7 @@ def print_systematics(self): pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") + return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2)) def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from @@ -557,6 +559,21 @@ def print_SNRs(self, rho_opt): logger.info("Noise temperature: {}K".format(self.noise_temp/K)) logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms + +# def return_extra_info(self, rho_opt): +# # Func to return additional relevant parameter values for output in sensitivityparameterscan +# tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) +# logger.info("tau_SNR: {}s".format(tau_snr/s)) +# eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) +# SNR_1eV = 1/eV_bandwidth/2./tau_snr +# track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) +# SNR_track_duration = track_duration/2./tau_snr +# SNR_1ms = 0.001*s/2./tau_snr +# +# labels, sigmas, deltas = self.get_systematics() +# # noise_temp, SNR, track_duration, systematic limit, total sigma +# return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms, np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2)) def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 14cd824d..210c4b25 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -154,10 +154,33 @@ def InternalRun(self): logger.info('Adding goal: {} = {}'.format(key, value)) self.add_goal(value, key) - + # arrays for results values for output self.optimum_limits = [] self.optimum_rhos = [] - + + self.noise_temp = [] + self.SNR = [] + self.track_duration = [] + self.total_sigma = [] + self.sys_lim = [] + ''' + # Define some functions used for calcing results values + tritium_endpoint_molecular = 18574.01*eV + tritium_endpoint_atomic = 18563.251*eV + tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 + tritium_electron_crosssection_atomic = 9.e-23*m**2 + def gamma(kin_energy): + return kin_energy/(me*c0**2) + 1 + def beta(kin_energy): + return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) + def frequency(kin_energy, magnetic_field): + return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field + def track_length(rho, kin_energy=None, molecular=True): + if kin_energy is None: + kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic + crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic + return 1 / (rho * crosssect * beta(kin_energy) * c0) + ''' for i, color in self.range(self.scan_parameter_values/self.scan_parameter_unit): parameter_value = self.scan_parameter_values[i] @@ -230,8 +253,13 @@ def InternalRun(self): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) - + self.sens_main.print_SNRs(rho_opt) + noise_temp, SNR, track_duration = self.sens_main.print_SNRs(rho_opt) + # Store relevant values + self.noise_temp.append(noise_temp) + self.SNR.append(SNR) + self.track_duration.append(track_duration) logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -244,6 +272,9 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() + systematic_limit, total_sigma = self.sens_main.print_systematics() + self.sys_lim.append(systematic_limit) + self.total_sigma.append(total_sigma) self.save("sensitivity_vs_density_for_{}_scan.pdf".format(param)) @@ -251,12 +282,15 @@ def InternalRun(self): # plot and print best limits self.results = {"scan_parameter": self.scan_parameter_name, "scan parameter_unit": self.scan_parameter_unit_string, "scan_parameter_values": self.scan_parameter_values, "optimum_limits_eV": np.array(self.optimum_limits)/eV, - "optimum_densities/m3": np.array(self.optimum_rhos)*(m**3)} + "optimum_densities/m3": np.array(self.optimum_rhos)*(m**3), + "Noise Temperatures/K": np.array(self.noise_temp)/K, + "SNRs 1eV from temperature": np.array(self.SNR), "track durations": np.array(self.track_duration)/ms, + "Systematic limits": np.array(self.sys_lim), "Total Sigmas": np.array(self.total_sigma)} logger.info("Scan parameter: {}".format(self.scan_parameter_name)) logger.info("Tested parameter values: {}".format(self.scan_parameter_values/self.scan_parameter_unit)) logger.info("Best limits: {}".format(np.array(self.optimum_limits)/eV)) - + plt.figure(figsize=self.figsize) #plt.title("Sensitivity vs. {}".format(self.scan_parameter_name)) plt.plot(self.scan_parameter_values/self.scan_parameter_unit, np.array(self.optimum_limits)/eV, marker=".", label="Density optimized scenarios") diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index e725461b..b6039a44 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -13,7 +13,10 @@ import numpy as np # import all the scanned parameter units -from numericalunits import eV, K, mK, T # whatever you need +from numericalunits import e, me, c0, eps0, kB, hbar +from numericalunits import meV, eV, keV, MeV, cm, m, mm +from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W +from numericalunits import hour, year, day, ms, ns, s, Hz, kHz, MHz, GHz deg = np.pi/180 @@ -35,10 +38,14 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "MagneticField.sigmae_r", - "scan_parameter_range": [0.1, 5], - "scan_parameter_steps": 4, - "scan_parameter_unit": eV, + #"scan_parameter_name": "MagneticField.sigmae_r", + #"scan_parameter_range": [0.1, 5], + #"scan_parameter_steps": 4, + #"scan_parameter_unit": eV, + "scan_parameter_name": "Experiment.l_over_d", + "scan_parameter_range": [5, 15], + "scan_parameter_steps": 5, + "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e12, #0.0002 @@ -65,10 +72,14 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "FrequencyExtraction.minimum_angle_in_bandwidth", - "scan_parameter_range": [83, 89], - "scan_parameter_steps": 10, - "scan_parameter_unit": deg, + #"scan_parameter_name": "FrequencyExtraction.minimum_angle_in_bandwidth", + #"scan_parameter_range": [83, 89], + #"scan_parameter_steps": 10, + #"scan_parameter_unit": deg, + "scan_parameter_name": "Experiment.l_over_d", + "scan_parameter_range": [5, 15], + "scan_parameter_steps": 5, + "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False @@ -94,10 +105,14 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "Experiment.l_over_d", - "scan_parameter_range": [5, 8], - "scan_parameter_steps": 3, - "scan_parameter_unit": 1, + #"scan_parameter_name": "Experiment.l_over_d", + #"scan_parameter_range": [5, 8], + #"scan_parameter_steps": 3, + #"scan_parameter_unit": 1, + "scan_parameter_name": "Experiment.background_rate_per_ev", + "scan_parameter_range": [1e-10, 4e-10], + "scan_parameter_steps": 4, # This is the one that currently has text output + "scan_parameter_unit": 1/(eV*s), "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False From 148b644455d7e6d4562d72e2cf4d111c1fea0097 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 14 May 2024 12:26:03 -0400 Subject: [PATCH 129/262] Improving plot formatting --- test_analysis/LFA_Sensitivity.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index d8332c7e..f00e4b99 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -24,12 +24,15 @@ "molecular_axis": False, "atomic_axis": False, "density_axis": True, + "optimize_main_density": True, "cavity": True, #"y_limits": [2e-2, 20], "y_limits": [2e-1, 20], "density_range": [1e13,1e20], - "efficiency_range": [0.0001, 1], + #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], + "configure_sigma_theta_r": True, + "sigmae_theta_r": np.linspace(0.1, 0.5, 10), #in eV, energy broadening from theta and r reconstruction "main_curve_upper_label": r"$\sigma^B_\mathrm{corr} = 0.5\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", "main_curve_lower_label": r"$\sigma^B_\mathrm{corr} = 0.1\,\mathrm{eV}$", #r"Molecular"+"\n"+"Reaching target", #"comparison_curve_label": [#"Molecular, reaching target", @@ -49,9 +52,7 @@ "upper_label_y_position": 4, "label_x_position": 4e14, #4e14, #0.02, #1e14, "goals_x_position": 1.2e14, #0.0002 - "plot_key_parameters": True, - "configure_sigma_theta_r": True, - "sigmae_theta_r": np.linspace(0.1, 0.5, 10), #in eV, energy broadening from theta and r reconstruction + "plot_key_parameters": True } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) @@ -62,10 +63,11 @@ # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", "plot_path": "./lfa_sensitivity_vs_exposure_curve_1GHz.pdf", + "exposure_axis": True, # optional - "figsize": (8,6), + "figsize": (10,6), "fontsize": 15, - "legend_location": "upper center", + "legend_location": "upper right", "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, @@ -75,7 +77,7 @@ "y_limits": [10e-3, 500], "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], - "main_curve_upper_label": r"LFA (atomic T pilot) - 1 GHz", + "main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.4 eV)": (0.4**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, @@ -85,7 +87,7 @@ # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_label": [r"LFA (atomic T pilot) - 500 MHz", r"Phase IV scenario - 150 MHz"], + "comparison_curve_label": [r"LFA (atomic T)$\,-\,$500 MHz", r"Phase IV scenario$\,-\,$150 MHz"], #"comparison_curve_colors": ["blue", "darkred", "red"], "comparison_curve_colors": ["darkred", "red"], "optimize_main_density": False, From bd31e8bad9408127e21a74d00102c82d9e9e532b Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 14 May 2024 12:35:32 -0400 Subject: [PATCH 130/262] Clean section on Scripts used in sensitivity calculations --- documentation/sensitivity.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 52923f39..37d9c1c3 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -7,16 +7,17 @@ Scripts used in sensitivity calculations ---------------------------------- The mermithid sensitivity calculations are written in python and a python script can be used to configure the calculation and make sensitivity plots. -Mermithid has several processors to make this a lot esaier. For cavity sensitivity calculations that is the `CavitySensitivityCurveProcessor`_. +Mermithid has several processors for this purpose, in `mermithid/mermithid/processors/Sensitivity/`_. For cavity sensitivity calculations, use the `CavitySensitivityCurveProcessor`_. +.. _mermithid/mermithid/processors/Sensitivity/: https://github.com/project8/mermithid/tree/feature/sensitivity_curve/mermithid/processors/Sensitivity .. _CavitySensitivityCurveProcessor: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py Mermithid processors are all designed to be used in the same way: -1. Define a dictionary with the processors configurable parameters -2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method. -3. Call the processors ``Run()`` method to make it perform its task. +1. Define a dictionary with the processors configurable parameters; +2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method.; +3. Call the processor's ``Run()`` method to make it perform its task. -In `mermithid/tests`_ the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor`` the ``CavitySensitivityCurveProcessor`` is used as described above to calculate and plot the sensitivity of a cavity experiment to the neutrino mass as a function of gas density. +In `mermithid/tests`_, the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor``, the ``CavitySensitivityCurveProcessor`` is used as described above to calculate and plot sensitivity to the neutrino mass as a function of gas density. Other working examples for creating sensivitiy plots vs. frequency or exposure can be found in `mermithid/test_analyses/Cavity_Sensitivity_analysis.py`_. .. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/tests @@ -25,7 +26,7 @@ Other working examples for creating sensivitiy plots vs. frequency or exposure c Analytic sensitivity formula ---------------------------------- - +Test Systematic uncertainty contributions From b0f962d23dda459b2e5d76ea60647861bb037642 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 09:42:28 -0700 Subject: [PATCH 131/262] Updated readme --- README.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cac850c4..19f19822 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mermithid is an extension of [morpho](https://github.com/morphoorg/morpho) that ## Requirements -You will need to install via a package manager (such as apt-get): +If you are not using a container with pre-installed dependencies, you will need to install via a package manager (such as apt-get): - python (3.x; 2.7.x support not guaranteed) - python-pip @@ -19,6 +19,9 @@ These are two possible ways of installing and working with mermithid. ### Virtual environment installation +Before installing, clone subdirecories recursively: ``git submodule update --init --recursive``. +
Then install mermithid in your environment: + 1. Cicada and Phylloxera need to be installed in a sub directory: ```bash @@ -48,13 +51,21 @@ These are two possible ways of installing and working with mermithid. Docker provides a uniform test bed for development and bug testing. Please use this environment to testing/resolving bugs. 1. Install Docker (Desktop version): -2. Clone and pull the latest master version of mermithid -3. Inside the mermithid folder, execute `docker-compose run mermithid`. The container prompter should appear at the end of the installation. A directory (`mermithid_share`) should be created in your home and mounted under the `/host` folder: you can modify this by editing the docker-compose file. -4. When reinstalling, you can remove the image using `docker rmi mermithid_mermithid` +3. Clone and pull the latest main version of mermithid or the feature branch you want to work with +4. Go to the cloned directory: ``cd mermithid`` +5. Pull the submodules: ``git submodule update --init --recursive`` +6. Build docker image: ``docker build --no-cache -t mermithid: .`` +7. To start the container and mount a directory for data sharing with your host into the container do: +
```docker run --rm -it -v ~/mermithid_share:/host mermithid: /bin/bash``` + +An alternative to steps 6 and 7 is to use docker-compose by executing: ``docker-compose run mermithid``. + +
In both cases files saved in ```/host``` are shared with the host in ```~/mermithid_share```. + ### Running mermithid -In both cases, you need to set the paths right for using these software. For example in the docker container: +For running mermithid you need to set the paths right for using these software. For example in the docker container: ```bash source $MERMITHID_BUILD_PREFIX/setup.sh @@ -64,4 +75,11 @@ source $MERMITHID_BUILD_PREFIX/bin/this_phylloxera.sh ## Quick start and examples -Mermithid works a-la morpho, where the operations on data are defined using processors. Each processor should be defined with a name, then should have its attributes configured using a dictionary before being run. Examples of how to use mermithid can be found in the "tests" folder. +Mermithid works a-la morpho, where the operations on data are defined using processors. Each processor should be defined with a name, then should have its attributes configured using a dictionary before being run. Examples of how to use mermithid can be found in the "tests" and the "test_analysis" folders. + + +## Easy development + +To develop mermithid without having to rebuild the container, share the repository on the host with the container by starting it with: ```docker run --rm -it -v ~/mermithid_share:/host -v ~/repos/mermithid:/mermithid mermithid: /bin/bash```. + +Then, after sourcing the setup scripts modify the PYTHONPATH ```export PYTHONPATH=/mermithid:$PYTHONPATH```. Now changes made on the host will directly be used by the container. From 744f267e8cfd50782a895f1e52dc480dea8d3d86 Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 14 May 2024 12:49:08 -0400 Subject: [PATCH 132/262] Add info re config files/documentation; improve formatting --- documentation/sensitivity.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 37d9c1c3..8c3dab3b 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -1,10 +1,10 @@ ------------------- +-------------------------------------- Neutrino Mass Sensitivity Calculation ------------------- +-------------------------------------- Scripts used in sensitivity calculations ----------------------------------- +------------------------------------------- The mermithid sensitivity calculations are written in python and a python script can be used to configure the calculation and make sensitivity plots. Mermithid has several processors for this purpose, in `mermithid/mermithid/processors/Sensitivity/`_. For cavity sensitivity calculations, use the `CavitySensitivityCurveProcessor`_. @@ -13,8 +13,8 @@ Mermithid has several processors for this purpose, in `mermithid/mermithid/proce .. _CavitySensitivityCurveProcessor: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py Mermithid processors are all designed to be used in the same way: -1. Define a dictionary with the processors configurable parameters; -2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method.; +1. Define a dictionary with the processor's configurable parameters; +2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method; 3. Call the processor's ``Run()`` method to make it perform its task. In `mermithid/tests`_, the ``Sensitivity_test.py`` script contains several examples of how to perform sensitivity calculations using mermithid processors. In ``test_SensitivityCurveProcessor``, the ``CavitySensitivityCurveProcessor`` is used as described above to calculate and plot sensitivity to the neutrino mass as a function of gas density. @@ -23,14 +23,18 @@ Other working examples for creating sensivitiy plots vs. frequency or exposure c .. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/tests .. _mermithid/test_analyses/Cavity_Sensitivity_analysis.py: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/test_analysis/Cavity_Sensitivity_analysis.py +The dictionary to configure the processor can read in a separate configuration file (`.cfg`) with sensitivity-specific parameters. The documentation page `sensitivity_configurable_parameters.rst`_ describes all parameters in such a `.cfg`. + +.. _sensitivity_configurable_parameters.rst: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/documentation/sensitivity_configurable_parameters.rst + Analytic sensitivity formula ----------------------------------- +----------------------------------- Test Systematic uncertainty contributions ----------------------------------- +------------------------------------- The following contributions to energy broadening of the beta spectrum are included: @@ -51,7 +55,7 @@ Contributions 4 and 5 are simply inputted in the sensitivity configuration file; Translational Doppler broadening (``sigma_trans``) ============================ -The thermal translational motion of tritium atoms causes a Doppler broadening of the $\beta$ energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: +The thermal translational motion of tritium atoms causes a Doppler broadening of the :math:`{\beta}` energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: 1. Manually input values for ``sigma_trans`` and its uncertainty ``delta_trans``, calculated outside of mermithid. This is done in the ``DopplerBroadening`` section of the configuration file, by setting ``UseFixedValue`` to ``True`` and providing values for ``Default_Systematic_Smearing`` and ``Default_Systematic_Uncertainty``. From 426a938b59d798896511768e179286f0462fbb03 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 09:49:58 -0700 Subject: [PATCH 133/262] fixes in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 19f19822..d4af8611 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Before installing, clone subdirecories recursively: ``git submodule update --ini ### Docker installation -Docker provides a uniform test bed for development and bug testing. Please use this environment to testing/resolving bugs. +Create a docker image and start a mermithid container 1. Install Docker (Desktop version): 3. Clone and pull the latest main version of mermithid or the feature branch you want to work with @@ -80,6 +80,6 @@ Mermithid works a-la morpho, where the operations on data are defined using proc ## Easy development -To develop mermithid without having to rebuild the container, share the repository on the host with the container by starting it with: ```docker run --rm -it -v ~/mermithid_share:/host -v ~/repos/mermithid:/mermithid mermithid: /bin/bash```. +To develop mermithid without having to rebuild the container, share the repository on the host with the container by starting it with: ```docker run --rm -it -v ~/mermithid_share:/host -v ~/repos/mermithid:/mermithid mermithid: /bin/bash```. This assumes that mermithid was cloned to ``~/repos``. -Then, after sourcing the setup scripts modify the PYTHONPATH ```export PYTHONPATH=/mermithid:$PYTHONPATH```. Now changes made on the host will directly be used by the container. +After sourcing the setup scripts, modify the PYTHONPATH: ```export PYTHONPATH=/mermithid:$PYTHONPATH```. Now changes made on the host will directly be used by the container. From fce82dac6acc5988495aeeb3c7723628fa31ad3e Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 14 May 2024 12:53:35 -0400 Subject: [PATCH 134/262] Fix more formatting --- documentation/sensitivity.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index 8c3dab3b..ee28bd54 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -13,6 +13,7 @@ Mermithid has several processors for this purpose, in `mermithid/mermithid/proce .. _CavitySensitivityCurveProcessor: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py Mermithid processors are all designed to be used in the same way: + 1. Define a dictionary with the processor's configurable parameters; 2. Instantiate a processor and pass the configuration dictionary to its ``Configure()`` method; 3. Call the processor's ``Run()`` method to make it perform its task. @@ -23,7 +24,7 @@ Other working examples for creating sensivitiy plots vs. frequency or exposure c .. _mermithid/tests: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/tests .. _mermithid/test_analyses/Cavity_Sensitivity_analysis.py: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/test_analysis/Cavity_Sensitivity_analysis.py -The dictionary to configure the processor can read in a separate configuration file (`.cfg`) with sensitivity-specific parameters. The documentation page `sensitivity_configurable_parameters.rst`_ describes all parameters in such a `.cfg`. +The dictionary to configure the processor (see #1, above) can read in a separate configuration file (``.cfg``) with sensitivity-specific parameters. The documentation page `sensitivity_configurable_parameters.rst`_ describes all parameters in such a ``.cfg``. .. _sensitivity_configurable_parameters.rst: https://github.com/project8/mermithid/blob/feature/sensitivity_curve/documentation/sensitivity_configurable_parameters.rst @@ -54,7 +55,7 @@ Contributions 4 and 5 are simply inputted in the sensitivity configuration file; Translational Doppler broadening (``sigma_trans``) -============================ +======================================================== The thermal translational motion of tritium atoms causes a Doppler broadening of the :math:`{\beta}` energy spectrum. ``sigma_trans`` is the standard deviation of this broadening distribution. There are two options for how to include translational Doppler broadening in your sensitivity calculations, in mermithid: 1. Manually input values for ``sigma_trans`` and its uncertainty ``delta_trans``, calculated outside of mermithid. @@ -65,9 +66,9 @@ This is done in the ``DopplerBroadening`` section of the configuration file, by - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. Calculation of ``sigma_trans`` for option 2: -For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation -.. math:: :name:eq:sigtrans \sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}, -where :math:`m_T` is the mass of tritium and :math:`T` is the gas temperature. +For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation +.. math:: :name:eq:sigtrans `{\sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}}`, +where :math:`{m_T}` is the mass of tritium and :math:`{T}` is the gas temperature. Calculation of ``delta_trans`` for option 2, with atomic T: @@ -75,9 +76,9 @@ Calculation of ``delta_trans`` for option 2, with atomic T: Track start frequency determination and pitch angle correction (``sigma_f``) -============================ +==================================================================================== Radial, azimuthal, and temporal field broadening (``sigma_B``) -============================ +==================================================================================== From 601786b06ee7155e2c2b46457156fde0dd9bf7cf Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Tue, 14 May 2024 12:59:02 -0400 Subject: [PATCH 135/262] Fix math formatting --- documentation/sensitivity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index ee28bd54..e5f1c301 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -67,7 +67,7 @@ This is done in the ``DopplerBroadening`` section of the configuration file, by Calculation of ``sigma_trans`` for option 2: For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation -.. math:: :name:eq:sigtrans `{\sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}}`, +:math:`{\sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}}`, where :math:`{m_T}` is the mass of tritium and :math:`{T}` is the gas temperature. From 8b03bc98b1b9fd98fcd490097fe996355f151345 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 09:59:19 -0700 Subject: [PATCH 136/262] hopefully fixed title --- documentation/sensitivity_configurable_parameters.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/sensitivity_configurable_parameters.rst b/documentation/sensitivity_configurable_parameters.rst index 81d55e6b..d461fb22 100644 --- a/documentation/sensitivity_configurable_parameters.rst +++ b/documentation/sensitivity_configurable_parameters.rst @@ -1,6 +1,6 @@ ------------------- +---------------------------------------------------- Configuring mermithid sensitivity calculation ------------------- +---------------------------------------------------- The sensitivity calculation is configured using a configuration file. The configuration files for Project 8 live in the termite repository: https://github.com/project8/termite/tree/feature/sensitivity_config_files/sensitivity_config_files From ca462fca852e68567b3cddf16db33b6973fc0709 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 10:04:27 -0700 Subject: [PATCH 137/262] Created readthedocks.yaml --- .readthedocs.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..0ed76f10 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,31 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: documentation/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +#python: +# install: +# - requirements: Nymph/Scarab/documentation/cpp/requirements.txt \ No newline at end of file From 70dc13f26cacf977ec3a70197e150f39aac74c90 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 11:40:11 -0700 Subject: [PATCH 138/262] added spinx_rtd_theme --- documentation/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/conf.py b/documentation/conf.py index 1209ea2b..3b4a5e33 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -31,6 +31,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'spinx_rtd_theme', 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode', From d5590c6c133bda16cf299ecf64269ff408bbfb02 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 11:42:18 -0700 Subject: [PATCH 139/262] typo --- documentation/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/conf.py b/documentation/conf.py index 3b4a5e33..ba93a953 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -31,7 +31,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'spinx_rtd_theme', + 'sphinx_rtd_theme', 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode', From 9fe855d1f6d293e3a1f28ba28c890364ce46a0f7 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:01:13 -0700 Subject: [PATCH 140/262] trying requirements.txt --- .readthedocs.yaml | 6 +++--- documentation/requirements.txt | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 documentation/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 0ed76f10..5394f134 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -26,6 +26,6 @@ sphinx: # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -#python: -# install: -# - requirements: Nymph/Scarab/documentation/cpp/requirements.txt \ No newline at end of file +python: + install: + - requirements: documentation/requirements.txt \ No newline at end of file diff --git a/documentation/requirements.txt b/documentation/requirements.txt new file mode 100644 index 00000000..5cfdbfca --- /dev/null +++ b/documentation/requirements.txt @@ -0,0 +1,6 @@ +sphinx +sphinx_rtd_theme +sphinxcontrib-programoutput +six +colorlog +better-apidoc \ No newline at end of file From d927a6330afc19c0e8361f646c1a0fa2e61c89dd Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:06:02 -0700 Subject: [PATCH 141/262] just sphinx_rtd_theme --- .readthedocs.yaml | 2 +- documentation/requirements.txt | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5394f134..7c5ac611 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -28,4 +28,4 @@ sphinx: # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: documentation/requirements.txt \ No newline at end of file + - requirements: sphinx_rtd_theme \ No newline at end of file diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 5cfdbfca..52b04f2e 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,6 +1 @@ -sphinx -sphinx_rtd_theme -sphinxcontrib-programoutput -six -colorlog -better-apidoc \ No newline at end of file +sphinx_rtd_theme \ No newline at end of file From e65b5cc4895b8783656fdd10297c0309a989f607 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:08:23 -0700 Subject: [PATCH 142/262] another way --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7c5ac611..5394f134 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -28,4 +28,4 @@ sphinx: # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - - requirements: sphinx_rtd_theme \ No newline at end of file + - requirements: documentation/requirements.txt \ No newline at end of file From 8b6f39b2884f88649f31bc63c2d1b8508cf371ea Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:09:57 -0700 Subject: [PATCH 143/262] adding better-apidoc in requirement --- documentation/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 52b04f2e..8ee16222 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1 +1,2 @@ -sphinx_rtd_theme \ No newline at end of file +sphinx_rtd_theme +better-apidoc \ No newline at end of file From e1a044ff87b35be27394bb77333599a703c5fbff Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:11:12 -0700 Subject: [PATCH 144/262] more requirements --- documentation/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 8ee16222..3db0dd3f 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,2 +1,5 @@ sphinx_rtd_theme +sphinxcontrib-programoutput +six +colorlog better-apidoc \ No newline at end of file From f425bc9515c6010c74af18be3dba654f6895c355 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:19:22 -0700 Subject: [PATCH 145/262] in case the order matters --- documentation/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 3db0dd3f..046e45e2 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,5 +1,5 @@ +better-apidoc sphinx_rtd_theme sphinxcontrib-programoutput six -colorlog -better-apidoc \ No newline at end of file +colorlog \ No newline at end of file From e3fb414eb972bd0d0feff5fdd68e7b7faf05ebeb Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 14 May 2024 12:20:50 -0700 Subject: [PATCH 146/262] dunno --- documentation/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 046e45e2..dbbbc8bf 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,5 +1,5 @@ -better-apidoc +six sphinx_rtd_theme sphinxcontrib-programoutput -six -colorlog \ No newline at end of file +colorlog +better-apidoc \ No newline at end of file From fb107165799e9ee78be5cc71e3c3c6e614b8b8fd Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 20 May 2024 14:24:24 -0700 Subject: [PATCH 147/262] started conf from scratch --- documentation/conf.py | 317 ++--------------------------------- documentation/contribute.rst | 5 +- documentation/index.rst | 37 ++-- 3 files changed, 46 insertions(+), 313 deletions(-) diff --git a/documentation/conf.py b/documentation/conf.py index ba93a953..fc3a269d 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -1,312 +1,31 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# morpho documentation build configuration file, created by -# sphinx-quickstart on Mon Aug 25 15:26:40 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx_rtd_theme', - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', - # 'sphinxcontrib.programoutput', - 'sphinx.ext.napoleon', - # 'sphinxarg.ext', -] - -# better_apidoc.main([ -# 'better_apidoc', -# '-t', '_templates', # path to jinja templates for generated package and module rst files -# '--force', # overwrite existing files -# '--separate', # split the modules into their own files -# # output location, be sure to update index.rst if you change this -# '-o', 'better_apidoc_out', -# '../morpho', # path to the package containing modules to document ##TODO update this with your path -# ]) - - -def run_apidoc(_): - """Generage API documentation""" - import better_apidoc - better_apidoc.main( - ['better-apidoc', '-t', './_templates', '--force', '--no-toc', - '--separate', '-o', 'better_apidoc_out', '../mermithid']) - - -def setup(app): - app.connect('builder-inited', run_apidoc) - -todo_include_todos = True - -autoclass_content = "both" - -# Add any paths that contain templates here, relative to this directory. -# templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -# The encoding of source files. -# source_encoding = 'utf-8-sig' +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -# The master toctree document. -master_doc = 'index' - -# General information about the project. -# TODO change this line to match the package name project = 'mermithid' -copyright = '2018, The Project 8 Collaboration' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -# version = #'0.0.1' -# import pkg_resources -# release: The full version, including alpha/beta/rc tags. -try: - import subprocess - release = subprocess.check_output( - ['git', 'describe', '--long']).decode('utf-8').strip() - version = subprocess.check_output( - ['git', 'describe', '--abbrev=0']).decode('utf-8').strip() -except Exception as e: - print("error message is:\n{}".format(e.message)) - version = "unknown" - release = "unknown" -print('version/release are: {}/{}'.format(version, release)) - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# try: -import sphinx_rtd_theme -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -html_theme = 'sphinx_rtd_theme' -# except ImportError: -# html_theme = 'haiku' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. - -# TODO put your favicon here -# html_favicon = 'morpho_favicon.ico' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'mermithiddoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, # author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'mermithid.tex', 'mermithid Documentation', - 'The Project 8 Collaboration', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- +copyright = '2024, The Project 8 Collaboration' +author = 'The Project 8 Collaboration' +release = '2018' -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'mermithid', 'mermithid Documentation', - ['The Project 8 Collaboration'], 1) -] +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# If true, show URL addresses after external links. -# man_show_urls = False +extensions = [] +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.md'] -# -- Options for Texinfo output ------------------------------------------- -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'mermithid', 'mermithid Documentation', - 'The Project 8 Collaboration', 'mermithid', 'One line description of project.', - 'Miscellaneous'), -] -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# If false, no module index is generated. -# texinfo_domain_indices = True +html_theme = 'alabaster' +html_static_path = ['_static'] -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False +extensions = ["myst_parser"] \ No newline at end of file diff --git a/documentation/contribute.rst b/documentation/contribute.rst index 2085fd76..dd6c9c53 100644 --- a/documentation/contribute.rst +++ b/documentation/contribute.rst @@ -34,7 +34,8 @@ Other Conventions In mermithid 1, __init__.py files are set up such that :: - from package import * + + from package import * will import all functions from all subpackages and modules into the namespace. If a package contains the subpackages "subpackage1" and "subpackage2", and the modules "module1" and "module2", then the __init__.py file should include imports of the form: :: @@ -54,5 +55,5 @@ will import all modules into the namespace, but it will not directly import the __all__ = ["module1", "module2"] -In this case, functions would be called via module1.function_name(). If one wants all of the functions from module1 in the namespace, then they can include "from package.module1 import *" at the top of their code. This change to more explicit imports should prevent any issues with function names clashing as mermithid grows. +In this case, functions would be called via module1.function_name(). If one wants all of the functions from module1 in the namespace, then they can include "from package.module1 import \*" at the top of their code. This change to more explicit imports should prevent any issues with function names clashing as mermithid grows. diff --git a/documentation/index.rst b/documentation/index.rst index bc953a3d..d4667d0e 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -1,15 +1,28 @@ -Welcome to mermithid's documentation! -==================================== +.. mermithid documentation master file, created by + sphinx-quickstart on Mon May 20 13:35:50 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. -Contents: +Welcome to mermithid's documentation! +===================================== .. toctree:: - :maxdepth: 2 - - intro - install - contribute - sensitivity - sensitivity_configurable_parameters - validation_log - better_apidoc_out/modules + :maxdepth: 2 + :caption: Contents: + + intro + howmermithidworks + install + contribute + sensitivity + sensitivity_configurable_parameters + validation_log + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` From 848f6c367027d261b0e01ae7c7989a7709766736 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 20 May 2024 14:34:19 -0700 Subject: [PATCH 148/262] trying different extension --- documentation/conf.py | 2 +- documentation/requirements.txt | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/documentation/conf.py b/documentation/conf.py index fc3a269d..648a6072 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -28,4 +28,4 @@ html_static_path = ['_static'] -extensions = ["myst_parser"] \ No newline at end of file +extensions = ['sphinxcontrib.contentui'] \ No newline at end of file diff --git a/documentation/requirements.txt b/documentation/requirements.txt index dbbbc8bf..6da3e1e6 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,5 +1 @@ -six -sphinx_rtd_theme -sphinxcontrib-programoutput -colorlog -better-apidoc \ No newline at end of file +sphinxcontrib-contentui \ No newline at end of file From a1d9c6dd1c3e6711398dd3f69a8fd04e02bc94b2 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Mon, 20 May 2024 15:07:50 -0700 Subject: [PATCH 149/262] switched to sphinx_rtd_theme --- documentation/conf.py | 4 ++-- documentation/requirements.txt | 3 ++- documentation/validation_log.rst | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/documentation/conf.py b/documentation/conf.py index 648a6072..f801a83b 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -24,8 +24,8 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' #'alabaster' html_static_path = ['_static'] -extensions = ['sphinxcontrib.contentui'] \ No newline at end of file +extensions = ['sphinxcontrib.contentui', 'sphinx_rtd_theme'] \ No newline at end of file diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 6da3e1e6..0f433de6 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1 +1,2 @@ -sphinxcontrib-contentui \ No newline at end of file +sphinxcontrib-contentui +sphinx_rtd_theme \ No newline at end of file diff --git a/documentation/validation_log.rst b/documentation/validation_log.rst index e148dc91..f408e467 100644 --- a/documentation/validation_log.rst +++ b/documentation/validation_log.rst @@ -8,7 +8,7 @@ Version: v1.2.3 ~~~~~~~~~~~~~~~~ Release Date: Tues July 20 2021 -'''''''''''''''''''''''''''''' +'''''''''''''''''''''''''''''''''' Fixes: ''''''''''''' @@ -112,7 +112,7 @@ Version: v1.1.8 ~~~~~~~~~~~~~~~ Release Date: Thur Apr 18 2019 -''''''''''''''''''''''''''''' +''''''''''''''''''''''''''''''''' New features: ''''''''''''' From 6d513b8a565ef6105b5702e2f4363f0a88330706 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 22 May 2024 09:16:13 -0700 Subject: [PATCH 150/262] commented apidoc in makefile. I think it is not used. --- documentation/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/Makefile b/documentation/Makefile index 8f0bb615..fd35489c 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -8,8 +8,8 @@ SPHINXPROJ = mermithid SOURCEDIR = . BUILDDIR = build -PY_CMD = import better_apidoc; better_apidoc.main(['better_apidoc','-t', '_templates','--force','--separate','-o','better_apidoc_out','../$(SPHINXPROJ)']) # overwrite existing files -APIDOC_CMD = python -c "$(PY_CMD)" +#PY_CMD = import better_apidoc; better_apidoc.main(['better_apidoc','-t', '_templates','--force','--separate','-o','better_apidoc_out','../$(SPHINXPROJ)']) # overwrite existing files +#APIDOC_CMD = python -c "$(PY_CMD)" # Put it first so that "make" without argument is like "make help". help: @@ -22,5 +22,5 @@ help: %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -betterapi: - @$(APIDOC_CMD) \ No newline at end of file +#betterapi: +# @$(APIDOC_CMD) From 3cca3d698d6e4b9ea7562aa8aa56319f8128eec8 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Wed, 22 May 2024 09:59:20 -0700 Subject: [PATCH 151/262] seems to work without betterapi. deleting it. --- documentation/Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/documentation/Makefile b/documentation/Makefile index fd35489c..3fd5dcea 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -8,8 +8,6 @@ SPHINXPROJ = mermithid SOURCEDIR = . BUILDDIR = build -#PY_CMD = import better_apidoc; better_apidoc.main(['better_apidoc','-t', '_templates','--force','--separate','-o','better_apidoc_out','../$(SPHINXPROJ)']) # overwrite existing files -#APIDOC_CMD = python -c "$(PY_CMD)" # Put it first so that "make" without argument is like "make help". help: @@ -22,5 +20,3 @@ help: %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -#betterapi: -# @$(APIDOC_CMD) From 90686611295526f746b38c4921bdf1c554d42427 Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 24 May 2024 10:12:17 -0700 Subject: [PATCH 152/262] adding prints for efficiency and crlb in case of nonzero slope --- mermithid/misc/Constants_numericalunits.py | 2 +- .../CavitySensitivityCurveProcessor.py | 4 +++ .../sensitivity/SensitivityCavityFormulas.py | 32 +++++++++++++++---- test_analysis/Cavity_Sensitivity_analysis.py | 15 +++++---- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/mermithid/misc/Constants_numericalunits.py b/mermithid/misc/Constants_numericalunits.py index f5e8923f..e1577c41 100644 --- a/mermithid/misc/Constants_numericalunits.py +++ b/mermithid/misc/Constants_numericalunits.py @@ -6,7 +6,7 @@ import numpy as np from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ +from numericalunits import meV, eV, keV, MeV, mm, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W from numericalunits import hour, year, day, s, ms from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 6695560a..0de9b295 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -302,6 +302,7 @@ def InternalRun(self): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + self.sens_main.print_Efficiencies() self.sens_main.print_SNRs(rho_opt) logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) @@ -626,6 +627,7 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) def add_Phase_II_exposure_sens_line(self, sens): + logger.warning("Adding Phase II sensitivity") sens.Experiment.number_density = 2.09e17/m**3 sens.effective_volume = 1.2*mm**3 sens.Experiment.sri_factor = 1 #0.389*0.918*0.32 @@ -636,6 +638,8 @@ def add_Phase_II_exposure_sens_line(self, sens): sens.print_systematics() sens.print_statistics() sens.sensitivity() + sens.print_Efficiencies() + sens.print_SNRs() logger.info("Phase II sensitivity for exposure {} calculated: {}".format(standard_exposure, sens.sensitivity()/eV**2)) # Phase II experimental results diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 14dce653..e3730f9c 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -139,13 +139,13 @@ def EffectiveVolume(self): self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency else: # radial and detection efficiency are configured in the config file - logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) - logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) - logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) - logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + #logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) + #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() - logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) + #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor # for parent SignalRate function @@ -296,11 +296,13 @@ def syst_frequency_extraction(self): sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.best_time_window = self.time_window - """ non constant slope + # non constant slope + self.sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + """ CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) @@ -405,4 +407,20 @@ def print_SNRs(self, rho=None): logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(self.sigma_f_CRLB_slope_fitted/Hz)) + logger.info("CRLB constant: {}".format(self.CRLB_constant)) + return self.noise_temp, SNR_1eV, track_duration + + + def print_Efficiencies(self): + + if not self.Efficiency.usefixedvalue: + # radial and detection efficiency are configured in the config file + logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) + logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + + logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) + logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 747ea5c1..f8d38bc1 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -28,6 +28,7 @@ "molecular_axis": False, "atomic_axis": False, "density_axis": False, + "exposure_axis": True, "cavity": True, "add_PhaseII": True, "add_1year_1cav_point_to_last_ref": True, @@ -40,7 +41,7 @@ "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], "comparison_curve_colors": ["blue", "darkred", "red"], @@ -53,9 +54,9 @@ "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() # Configuration for Sensitivity vs. frequency plot @@ -180,9 +181,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() # Configuration for Sensitivity vs. density plot for best possible molecular scenario From 8f19317c4e1d4d4c8400a252c37a3323532c12e2 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Fri, 24 May 2024 12:56:20 -0700 Subject: [PATCH 153/262] Fixed miss-handling of units for outputting scan parameters --- mermithid/misc/SensitivityCavityFormulas.py | 4 ++-- .../Sensitivity/SensitivityParameterScanProcessor.py | 2 +- test_analysis/Sensitivity_parameter_scan.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mermithid/misc/SensitivityCavityFormulas.py b/mermithid/misc/SensitivityCavityFormulas.py index dfd9e7d8..a93c26d3 100644 --- a/mermithid/misc/SensitivityCavityFormulas.py +++ b/mermithid/misc/SensitivityCavityFormulas.py @@ -460,7 +460,7 @@ def print_systematics(self): pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") - return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2)) + return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from @@ -557,7 +557,7 @@ def print_SNRs(self, rho_opt): logger.info("Received power: {}W".format(self.received_power/W)) logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) logger.info("Noise temperature: {}K".format(self.noise_temp/K)) - logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 210c4b25..db46ef2d 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -283,7 +283,7 @@ def track_length(rho, kin_energy=None, molecular=True): self.results = {"scan_parameter": self.scan_parameter_name, "scan parameter_unit": self.scan_parameter_unit_string, "scan_parameter_values": self.scan_parameter_values, "optimum_limits_eV": np.array(self.optimum_limits)/eV, "optimum_densities/m3": np.array(self.optimum_rhos)*(m**3), - "Noise Temperatures/K": np.array(self.noise_temp)/K, + "Noise Temperatures/K": np.array(self.noise_temp), "SNRs 1eV from temperature": np.array(self.SNR), "track durations": np.array(self.track_duration)/ms, "Systematic limits": np.array(self.sys_lim), "Total Sigmas": np.array(self.total_sigma)} diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index b6039a44..cc3a8f01 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -109,10 +109,10 @@ #"scan_parameter_range": [5, 8], #"scan_parameter_steps": 3, #"scan_parameter_unit": 1, - "scan_parameter_name": "Experiment.background_rate_per_ev", - "scan_parameter_range": [1e-10, 4e-10], - "scan_parameter_steps": 4, # This is the one that currently has text output - "scan_parameter_unit": 1/(eV*s), + "scan_parameter_name": "Efficiency.radial_efficiency", + "scan_parameter_range": [0.55, 0.85], + "scan_parameter_steps": 7, # This is the one that currently has text output + "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False From 4ef313c220febadcd4b01232fe6e0323ed674495 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 28 May 2024 13:17:30 -0700 Subject: [PATCH 154/262] Finished merging, see some logging errors tho --- .../SensitivityParameterScanProcessor.py | 24 - .../sensitivity/SensitivityCavityFormulas.py | 511 +++--------------- mermithid/sensitivity/SensitivityFormulas.py | 1 + test_analysis/Sensitivity_parameter_scan.py | 4 +- 4 files changed, 86 insertions(+), 454 deletions(-) mode change 100644 => 100755 mermithid/sensitivity/SensitivityFormulas.py diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 0e3603df..6e29b252 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -163,24 +163,6 @@ def InternalRun(self): self.track_duration = [] self.total_sigma = [] self.sys_lim = [] - ''' - # Define some functions used for calcing results values - tritium_endpoint_molecular = 18574.01*eV - tritium_endpoint_atomic = 18563.251*eV - tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 - tritium_electron_crosssection_atomic = 9.e-23*m**2 - def gamma(kin_energy): - return kin_energy/(me*c0**2) + 1 - def beta(kin_energy): - return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) - def frequency(kin_energy, magnetic_field): - return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field - def track_length(rho, kin_energy=None, molecular=True): - if kin_energy is None: - kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic - crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic - return 1 / (rho * crosssect * beta(kin_energy) * c0) - ''' for i, color in self.range(self.scan_parameter_values/self.scan_parameter_unit): parameter_value = self.scan_parameter_values[i] @@ -249,18 +231,13 @@ def track_length(rho, kin_energy=None, molecular=True): if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) -<<<<<<< HEAD - self.sens_main.print_SNRs(rho_opt) noise_temp, SNR, track_duration = self.sens_main.print_SNRs(rho_opt) # Store relevant values self.noise_temp.append(noise_temp) self.SNR.append(SNR) self.track_duration.append(track_duration) -======= - self.sens_main.print_SNRs() ->>>>>>> feature/sensitivity_curve logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -272,7 +249,6 @@ def track_length(rho, kin_energy=None, molecular=True): self.sens_main.tau_tritium*2)) self.sens_main.print_statistics() - self.sens_main.print_systematics() systematic_limit, total_sigma = self.sens_main.print_systematics() self.sys_lim.append(systematic_limit) self.total_sigma.append(total_sigma) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index a93c26d3..b8b43af3 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -7,50 +7,12 @@ CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np -import configparser -from numpy import pi -from mermithid.misc.HannekeFunctions import * -# Numericalunits is a package to handle units and some natural constants -# natural constants -from numericalunits import e, me, c0, eps0, kB, hbar -from numericalunits import meV, eV, keV, MeV, cm, m, ns, s, Hz, kHz, MHz, GHz, amu, nJ -from numericalunits import nT, uT, mT, T, mK, K, C, F, g, W -from numericalunits import hour, year, day, s, ms -from numericalunits import mu0, NA, kB, hbar, me, c0, e, eps0, hPlanck +from mermithid.misc.Constants_numericalunits import * +from mermithid.misc.CRESFunctions_numericalunits import * +from mermithid.cavity.HannekeFunctions import * +from mermithid.sensitivity.SensitivityFormulas import * -T0 = -273.15*K - -tritium_livetime = 5.605e8*s -tritium_mass_atomic = 3.016* amu *c0**2 -tritium_electron_crosssection_atomic = 9.e-23*m**2 #Hamish extrapolated to 18.6keV using Shah et al. (1987): https://iopscience.iop.org/article/10.1088/0022-3700/20/14/022 -tritium_endpoint_atomic = 18563.251*eV -last_1ev_fraction_atomic = 2.067914e-13/eV**3 - -tritium_mass_molecular = 6.032099 * amu *c0**2 -tritium_electron_crosssection_molecular = 3.67*1e-22*m**2 #[Inelastic from Aseev (2000) for T2] + [Elastic from Liu (1987) for H2, extrapolated by Elise to 18.6keV] -tritium_endpoint_molecular = 18574.01*eV -last_1ev_fraction_molecular = 1.67364e-13/eV**3 - -ground_state_width = 0.436 * eV -#ground_state_width_uncertainty = 0.001*0.436*eV - -gyro_mag_ratio_proton = 42.577*MHz/T - -# units that do not show up in numericalunits -# missing pre-factors -fW = W*1e-15 - -# unitless units, relative fractions -pc = 0.01 -ppm = 1e-6 -ppb = 1e-9 -ppt = 1e-12 -ppq = 1e-15 - -# radian and degree which are also not really units -rad = 1 -deg = np.pi/180 try: @@ -59,52 +21,7 @@ except: print("Run without morpho!") -class NameSpace(object): - def __init__(self, iteritems): - if type(iteritems) == dict: - iteritems = iteritems.items() - for k, v in iteritems: - setattr(self, k.lower(), v) - def __getattribute__(self, item): - return object.__getattribute__(self, item.lower()) - -############################################################################## -# CRES functions -def gamma(kin_energy): - return kin_energy/(me*c0**2) + 1 - -def beta(kin_energy): - # electron speed at kin_energy - return np.sqrt(kin_energy**2+2*kin_energy*me*c0**2)/(kin_energy+me*c0**2) - -def frequency(kin_energy, magnetic_field): - # cyclotron frequency - return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field - -def wavelength(kin_energy, magnetic_field): - return c0/frequency(kin_energy, magnetic_field) - -def kin_energy(freq, magnetic_field): - return (e*c0**2/(2*np.pi*freq)*magnetic_field - me*c0**2) - -def rad_power(kin_energy, pitch, magnetic_field): - # electron radiation power - f = frequency(kin_energy, magnetic_field) - b = beta(kin_energy) - Pe = 2*np.pi*(e*f*b*np.sin(pitch/rad))**2/(3*eps0*c0*(1-b**2)) - return Pe - -def track_length(rho, kin_energy=None, molecular=True): - if kin_energy is None: - kin_energy = tritium_endpoint_molecular if molecular else tritium_endpoint_atomic - crosssect = tritium_electron_crosssection_molecular if molecular else tritium_electron_crosssection_atomic - return 1 / (rho * crosssect * beta(kin_energy) * c0) - -def sin2theta_sq_to_Ue4_sq(sin2theta_sq): - return 0.5*(1-np.sqrt(1-sin2theta_sq**2)) - -def Ue4_sq_to_sin2theta_sq(Ue4_sq): - return 4*Ue4_sq*(1-Ue4_sq) + # Wouters functinos def db_to_pwr_ratio(q_db): @@ -120,7 +37,7 @@ def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, m # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. #y = (90-max_pitch_angle)/4 - phi_rad = (90-max_pitch_angle)/180*pi + phi_rad = (90-max_pitch_angle)/180*np.pi return 0.16*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) #return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) @@ -174,7 +91,7 @@ def t_effective(t_physical, cyclotron_frequency): return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) ############################################################################### -class CavitySensitivity(object): +class CavitySensitivity(Sensitivity): """ Documentation: * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 @@ -183,46 +100,15 @@ class CavitySensitivity(object): * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ def __init__(self, config_path): - self.cfg = configparser.ConfigParser() - with open(config_path, 'r') as configfile: - self.cfg.read_file(configfile) - - # display configuration - try: - logger.info("Config file content:") - for sect in self.cfg.sections(): - logger.info(' Section: {}'.format(sect)) - for k,v in self.cfg.items(sect): - logger.info(' {} = {}'.format(k,v)) - except: - pass - - self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - - self.tau_tritium = tritium_livetime - if self.Experiment.atomic: - self.T_mass = tritium_mass_atomic - self.Te_crosssection = tritium_electron_crosssection_atomic - self.T_endpoint = tritium_endpoint_atomic - self.last_1ev_fraction = last_1ev_fraction_atomic - else: - self.T_mass = tritium_mass_molecular - self.Te_crosssection = tritium_electron_crosssection_molecular - self.T_endpoint = tritium_endpoint_molecular - self.last_1ev_fraction = last_1ev_fraction_molecular - - - self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) - self.FrequencyExtraction = NameSpace({opt: eval(self.cfg.get('FrequencyExtraction', opt)) for opt in self.cfg.options('FrequencyExtraction')}) - self.MagneticField = NameSpace({opt: eval(self.cfg.get('MagneticField', opt)) for opt in self.cfg.options('MagneticField')}) - self.FinalStates = NameSpace({opt: eval(self.cfg.get('FinalStates', opt)) for opt in self.cfg.options('FinalStates')}) - self.MissingTracks = NameSpace({opt: eval(self.cfg.get('MissingTracks', opt)) for opt in self.cfg.options('MissingTracks')}) - self.PlasmaEffects = NameSpace({opt: eval(self.cfg.get('PlasmaEffects', opt)) for opt in self.cfg.options('PlasmaEffects')}) + Sensitivity.__init__(self, config_path) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) self.CRLB_constant = 12 #self.CRLB_constant = 90 - + if hasattr(self.FrequencyExtraction, "crlb_constant"): + self.CRLB_constant = self.FrequencyExtraction.crlb_constant + logger.info("Using configured CRLB constant") + self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -252,14 +138,18 @@ def EffectiveVolume(self): self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency else: # radial and detection efficiency are configured in the config file - logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) - logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) - logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) - logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + #logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) + #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() - logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) + #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor + + # for parent SignalRate function + self.Experiment.v_eff = self.effective_volume + return self.effective_volume def PitchDependentTrappingEfficiency(self): @@ -272,7 +162,8 @@ def CavityPower(self): #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W #np.random.seed(1) - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=1000), + + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, @@ -305,198 +196,11 @@ def CavityLoadedQ(self): return self.loaded_q # SENSITIVITY - def SignalRate(self): - """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - #self.EffectiveVolume() - signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium - if not self.Experiment.atomic: - if hasattr(self.Experiment, 'gas_fractions'): - avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) - signal_rate *= avg_n_T_atoms - else: - signal_rate *= 2 - if hasattr(self.Experiment, 'active_gas_fraction'): - signal_rate *= self.Experiment.active_gas_fraction - return signal_rate - - def BackgroundRate(self): - """background rate, can be calculated from multiple components. - Assumes that background rate is constant over considered energy / frequency range.""" - return self.Experiment.background_rate_per_eV - - def SignalEvents(self): - """Number of signal events.""" - return self.SignalRate()*self.Experiment.LiveTime*self.DeltaEWidth()**3 - - def BackgroundEvents(self): - """Number of background events.""" - return self.BackgroundRate()*self.Experiment.LiveTime*self.DeltaEWidth() - - def DeltaEWidth(self): - """optimal energy bin width""" - labels, sigmas, deltas = self.get_systematics() - return np.sqrt(self.BackgroundRate()/self.SignalRate() - + 8*np.log(2)*(np.sum(sigmas**2))) - - def StatSens(self): - """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" - sig_rate = self.SignalRate() - DeltaE = self.DeltaEWidth() - sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE - +self.BackgroundRate()*self.Experiment.LiveTime/DeltaE) - return sens - - def SystSens(self): - """Pure systematic componenet to sensitivity""" - labels, sigmas, deltas = self.get_systematics() - sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) - return sens - - def sensitivity(self, **kwargs): - """Combined statisical and systematic uncertainty. - Using kwargs settings in namespaces can be changed. - Example how to change number density which lives in namespace Experiment: - self.sensitivity(Experiment={"number_density": rho}) - """ - for sect, options in kwargs.items(): - for opt, val in options.items(): - self.__dict__[sect].__dict__[opt] = val - - StatSens = self.StatSens() - SystSens = self.SystSens() - - # Standard deviation on a measurement of m_beta**2 assuming a mass of zero - sigma_m_beta_2 = np.sqrt(StatSens**2 + SystSens**2) - return sigma_m_beta_2 - - def CL90(self, **kwargs): - """ Gives 90% CL upper limit on neutrino mass.""" - # 90% of gaussian are contained in +-1.64 sigma region - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - - def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) - - # PHYSICS Functions - - def AvgNumTAtomsPerParticle_MolecularExperiment(self, gas_fractions, H2_type_gas_fractions): - """ - Given gas composition info (H2 vs. other gases, and how much of each H2-type isotopolog), returns an average number of tritium atoms per gas particle. - - Inputs: - - gas_fractions: dict of composition fractions of each gas (different from scatter fractions!); all H2 isotopologs are combined under key 'H2' - - H2_type_gas_fractions: dict with fraction of each isotopolog, out of total amount of H2 - """ - H2_iso_avg_num = 0 - for (key, val) in H2_type_gas_fractions.items(): - if key=='T2': - H2_iso_avg_num += 2*val - elif key=='HT' or key=='DT': - H2_iso_avg_num += val - elif key=='H2' or key=='HD' or key=='D2': - pass - logger.info("Activive fraction: {}".format(gas_fractions['H2']*H2_iso_avg_num)) - return gas_fractions['H2']*H2_iso_avg_num - - def BToKeErr(self, BErr, B): - return e*BErr/(2*np.pi*frequency(self.T_endpoint, B)/c0**2) - - def KeToBerr(self, KeErr, B): - return KeErr/e*(2*np.pi*frequency(self.T_endpoint, B)/c0**2) - - def track_length(self, rho): - return track_length(rho, self.T_endpoint, not self.Experiment.atomic) + # see parent class in SensitivityFormulas.py + # SYSTEMATICS - - def get_systematics(self): - """ Returns list of energy broadenings (sigmas) and - uncertainties on these energy broadenings (deltas) - for all considered systematics. We need to make sure - that we do not include effects twice or miss any - important effect. - - Returns: - * list of labels - * list of energy broadenings - * list of energy broadening uncertainties - """ - - # Different types of uncertainty contributions - sigma_trans, delta_sigma_trans = self.syst_doppler_broadening() - sigma_f, delta_sigma_f = self.syst_frequency_extraction() - sigma_B, delta_sigma_B = self.syst_magnetic_field() - sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() - sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() - - labels = ["Thermal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] - sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] - deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] - - if not self.Experiment.atomic: - labels.append("Molecular final state") - sigmas.append(ground_state_width) - deltas.append(self.FinalStates.ground_state_width_uncertainty_fraction*ground_state_width) - - return np.array(labels), np.array(sigmas), np.array(deltas) - - def print_statistics(self): - print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") - - def print_systematics(self): - labels, sigmas, deltas = self.get_systematics() - - print() - sigma_squared = 0 - for label, sigma, delta in zip(labels, sigmas, deltas): - print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") - sigma_squared += sigma**2 - sigma_total = np.sqrt(sigma_squared) - print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) - try: - print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") - except AttributeError: - pass - print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") - print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") - return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV - - def syst_doppler_broadening(self): - # estimated standard deviation of Doppler broadening distribution from - # translational motion of tritium atoms / molecules - # Predicted uncertainty on standard deviation, based on expected precision - # of temperature knowledge - if self.DopplerBroadening.UseFixedValue: - sigma = self.DopplerBroadening.Default_Systematic_Smearing - delta = self.DopplerBroadening.Default_Systematic_Uncertainty - return sigma, delta - - # thermal doppler broardening - gasTemp = self.DopplerBroadening.gas_temperature - mass_T = self.T_mass - endpoint = self.T_endpoint - - # these factors are mainly neglidible in the recoil equation below - E_rec = 3.409 * eV # maximal value # same for molecular tritium? - mbeta = 0*eV # term neglidible - betanu = 1 # neutrinos are fast - # electron-neutrino correlation term: 1 + 0.105(6)*betae*cosThetanu - # => expectation value of cosThetanu = 0.014 - cosThetaenu = 0.014 - - Ke = endpoint - betae = np.sqrt(Ke**2+2*Ke*me*c0**2)/(Ke+me*c0**2) ## electron speed at energy Ke - Emax = endpoint + me*c0**2 - Ee = endpoint + me*c0**2 - p_rec = np.sqrt( Emax**2-me**2*c0**4 + (Emax - Ee - E_rec)**2 - mbeta**2 + 2*Ee*(Emax - Ee - E_rec)*betae*betanu*cosThetaenu ) - sigma_trans = np.sqrt(p_rec**2/(2*mass_T)*2*kB*gasTemp) - - if self.Experiment.atomic == True: - delta_trans = np.sqrt(p_rec**2/(2*mass_T)*kB/gasTemp*self.DopplerBroadening.gas_temperature_uncertainty**2) - else: - delta_trans = sigma_trans*self.DopplerBroadening.fraction_uncertainty_on_doppler_broadening - return sigma_trans, delta_trans + # Generic systematics are implemented in the parent class in SensitivityFormulas.py def calculate_tau_snr(self, time_window, sideband_power_fraction=1): @@ -538,42 +242,7 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # end of Wouter's calculation return tau_snr - - def print_SNRs(self, rho_opt): - tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) - logger.info("tau_SNR: {}s".format(tau_snr/s)) - eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) - SNR_1eV = 1/eV_bandwidth/2./tau_snr - track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) - SNR_track_duration = track_duration/2./tau_snr - SNR_1ms = 0.001*s/2./tau_snr - logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) - logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) - logger.info("Track duration: {}ms".format(track_duration/ms)) - logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) - logger.info("SNR for track duration: {}".format(SNR_track_duration)) - logger.info("SNR for 1 ms: {}".format(SNR_1ms)) - logger.info("Received power: {}W".format(self.received_power/W)) - logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) - logger.info("Noise temperature: {}K".format(self.noise_temp/K)) - logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) - - return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms - -# def return_extra_info(self, rho_opt): -# # Func to return additional relevant parameter values for output in sensitivityparameterscan -# tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) -# logger.info("tau_SNR: {}s".format(tau_snr/s)) -# eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) -# SNR_1eV = 1/eV_bandwidth/2./tau_snr -# track_duration = track_length(rho_opt, self.T_endpoint, molecular=(not self.Experiment.atomic)) -# SNR_track_duration = track_duration/2./tau_snr -# SNR_1ms = 0.001*s/2./tau_snr -# -# labels, sigmas, deltas = self.get_systematics() -# # noise_temp, SNR, track_duration, systematic limit, total sigma -# return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms, np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2)) def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) @@ -628,11 +297,13 @@ def syst_frequency_extraction(self): sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor self.best_time_window = self.time_window - """ non constant slope + # non constant slope + self.sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + """ CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + 90*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) @@ -675,7 +346,9 @@ def syst_frequency_extraction(self): raise NotImplementedError("Unvertainty on CRLB for cavity noise calculation is not implemented.") def syst_magnetic_field(self): - + """ + Magnetic field uncertanty is in principle generic but its impact on efficiency depends on reconstruction and therefore on detector technology. + """ # magnetic field uncertainties can be decomposed in several part # * true magnetic field inhomogeneity # (would be there also without a trap) @@ -702,71 +375,53 @@ def syst_magnetic_field(self): return sigma, frac_uncertainty*sigma else: return 0, 0 - """ - BMapErr = self.MagneticField.probe_repeatability # Probe Repeatability - delta_BMapErr = self.MagneticField.probe_resolution # Probe resolution - - BFlatErr = self.MagneticField.BFlatErr # averaging over the flat part of the field - delta_BFlatErr = self.MagneticField.relative_uncertainty_BFlatErr*BFlatErr # UPDATE ? - - Delta_t_since_calib = self.MagneticField.time_since_calibration - shiftBdot = self.MagneticField.shift_Bdot - smearBdot = self.MagneticField.smear_Bdot - delta_shiftBdot = self.MagneticField.uncertainty_shift_Bdot - delta_smearBdot = self.MagneticField.uncertainty_smearBdot - BdotErr = Delta_t_since_calib * np.sqrt(shiftBdot**2 + smearBdot**2) - delta_BdotErr = Delta_t_since_calib**2/BdotErr * np.sqrt(shiftBdot**2 * delta_shiftBdot**2 + smearBdot**2 * delta_smearBdot**2) - - # position uncertainty is linear in wavelength - # position uncertainty is nearly constant w.r.t. radial position - # based on https://3.basecamp.com/3700981/buckets/3107037/uploads/3442593126 - rRecoErr = self.MagneticField.rRecoErr - delta_rRecoErr = self.MagneticField.relative_Uncertainty_rRecoErr * rRecoErr - - rRecoPhiErr = self.MagneticField.rRecoPhiErr - delta_rRecoPhiErr = self.MagneticField.relative_uncertainty_rRecoPhiErr * rRecoPhiErr - - rProbeErr = self.MagneticField.rProbeErr - delta_rProbeErr = self.MagneticField.relative_uncertainty_rProbeErr * rProbeErr - - rProbePhiErr = self.MagneticField.rProbePhiErr - delta_rProbePhiErr = self.MagneticField.relative_uncertainty_rProbePhiErr * rProbePhiErr - - Berr = np.sqrt(BMapErr**2 + - BFlatErr**2 + - BdotErr**2 + - rRecoErr**2 + - rRecoPhiErr**2 + - rProbeErr**2 + - rProbePhiErr**2) - - delta_Berr = 1/Berr * np.sqrt(BMapErr**2 * delta_BMapErr**2 + - BFlatErr**2 * delta_BFlatErr**2 + - BdotErr**2 * delta_BdotErr**2 + - rRecoErr**2 * delta_rRecoErr**2 + - rRecoPhiErr**2 * delta_rRecoPhiErr**2 + - rProbeErr**2 * delta_rProbeErr**2 + - rProbePhiErr**2 * delta_rProbePhiErr**2) - - return self.BToKeErr(Berr, B), self.BToKeErr(delta_Berr, B) - """ - - def syst_missing_tracks(self): - # this systematic should describe the energy broadening due to the line shape. - # Line shape is caused because you miss the first n tracks but then detect the n+1 - # track and you assume that this is the start frequency. - # This depends on the gas composition, density and cross-section. - if self.MissingTracks.UseFixedValue: - sigma = self.MissingTracks.Default_Systematic_Smearing - delta = self.MissingTracks.Default_Systematic_Uncertainty - return sigma, delta - else: - raise NotImplementedError("Missing track systematic is not implemented.") - - def syst_plasma_effects(self): - if self.PlasmaEffects.UseFixedValue: - sigma = self.PlasmaEffects.Default_Systematic_Smearing - delta = self.PlasmaEffects.Default_Systematic_Uncertainty - return sigma, delta - else: - raise NotImplementedError("Plasma effect sysstematic is not implemented.") + + + # PRINTS + def print_SNRs(self, rho=None): + logger.info("SNR parameters:") + if rho != None: + logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") + + track_duration = self.time_window + tau_snr = self.calculate_tau_snr(track_duration, sideband_power_fraction=1) + + + eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) + SNR_1eV = 1/eV_bandwidth/tau_snr + SNR_track_duration = track_duration/tau_snr + SNR_1ms = 0.001*s/tau_snr + + logger.info("Number density: {} m^-3".format(self.Experiment.number_density*m**3)) + logger.info("Track duration: {}ms".format(track_duration/ms)) + logger.info("tau_SNR: {}s".format(tau_snr/s)) + logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) + + logger.info("Received power: {}W".format(self.received_power/W)) + logger.info("Noise temperature: {}K".format(self.noise_temp/K)) + logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) + logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) + logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) + logger.info("SNR for track duration: {}".format(SNR_track_duration)) + logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + + + logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + + logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(self.sigma_f_CRLB_slope_fitted/Hz)) + logger.info("CRLB constant: {}".format(self.CRLB_constant)) + + return self.noise_temp/K, self.received_power/(self.noise_energy*eV_bandwidth), track_duration/ms + + + def print_Efficiencies(self): + + if not self.Efficiency.usefixedvalue: + # radial and detection efficiency are configured in the config file + logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) + logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + + logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) + logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py old mode 100644 new mode 100755 index 2fb7ee07..47589c9f --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -237,6 +237,7 @@ def print_systematics(self): pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") + return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index 7fe38d32..e949c140 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -109,8 +109,8 @@ #"scan_parameter_range": [5, 8], #"scan_parameter_steps": 3, #"scan_parameter_unit": 1, - "scan_parameter_name": "Efficiency.detection_efficiency", - "scan_parameter_range": [0.55, 0.85], + "scan_parameter_name": "Experiment.n_cavities", + "scan_parameter_range": [4, 16], "scan_parameter_steps": 7, # This is the one that currently has text output "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, From 66e3c1838eea0b420a31b0b44018cae000d6840d Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 28 May 2024 14:00:18 -0700 Subject: [PATCH 155/262] Fixed lingering issue with track duration units --- .../Sensitivity/SensitivityParameterScanProcessor.py | 2 +- test_analysis/Sensitivity_parameter_scan.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 6e29b252..aad9f7b0 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -261,7 +261,7 @@ def InternalRun(self): "scan_parameter_values": self.scan_parameter_values, "optimum_limits_eV": np.array(self.optimum_limits)/eV, "optimum_densities/m3": np.array(self.optimum_rhos)*(m**3), "Noise Temperatures/K": np.array(self.noise_temp), - "SNRs 1eV from temperature": np.array(self.SNR), "track durations": np.array(self.track_duration)/ms, + "SNRs 1eV from temperature": np.array(self.SNR), "track durations": np.array(self.track_duration), "Systematic limits": np.array(self.sys_lim), "Total Sigmas": np.array(self.total_sigma)} logger.info("Scan parameter: {}".format(self.scan_parameter_name)) diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index e949c140..b0cc1987 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -109,9 +109,9 @@ #"scan_parameter_range": [5, 8], #"scan_parameter_steps": 3, #"scan_parameter_unit": 1, - "scan_parameter_name": "Experiment.n_cavities", - "scan_parameter_range": [4, 16], - "scan_parameter_steps": 7, # This is the one that currently has text output + "scan_parameter_name": "FrequencyExtraction.sideband_power_fraction", + "scan_parameter_range": [0, .4], + "scan_parameter_steps": 9, # This is the one that currently has text output "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 From ac564ca2d190e1b3db5d6a450b1e0949e9e7963e Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 28 May 2024 15:15:13 -0700 Subject: [PATCH 156/262] fixed bug that had broken the Phase II sensitivity line in the CavitySensitivityCurveProcessor --- .../CavitySensitivityCurveProcessor.py | 37 +++++++++++-------- .../sensitivity/SensitivityCavityFormulas.py | 3 +- mermithid/sensitivity/SensitivityFormulas.py | 9 ++++- test_analysis/Cavity_Sensitivity_analysis.py | 5 ++- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 0de9b295..453731b4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -395,7 +395,8 @@ def create_plot(self): else: logger.info("Adding exposure axis") ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) - ax.tick_params(axis='x', which='minor', bottom=True) + #ax.tick_params(axis='x', which='minor', bottom=True) + #ax.tick_params(axis='y', which='minor', left=True) axis_label = r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" ax.set_xlabel(axis_label) @@ -597,8 +598,8 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): - limit = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] - opt = np.argmin(limit) + sigma_mbeta = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] + opt = np.argmin(sigma_mbeta) rho_opt = self.rhos[opt] sens.Experiment.number_density = rho_opt @@ -608,23 +609,23 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - self.ax.scatter([standard_exposure], [np.min(limit)], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) - limits = [] + sigma_mbetas = [] years = [] for ex in self.exposures: lt = ex/sens.EffectiveVolume() years.append(lt/year) sens.Experiment.livetime = lt - limits.append(sens.sensitivity()/eV**2) + sigma_mbetas.append(sens.sensitivity()/eV**2) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) - if sens.Experiment.atomic: + """if sens.Experiment.atomic: gas = "T" else: gas = r"T$_2$" - unit = r"m$^{-3}$" - self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) + unit = r"m$^{-3}$""" + self.ax.plot(self.exposures/m**3/year, sigma_mbetas, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) def add_Phase_II_exposure_sens_line(self, sens): logger.warning("Adding Phase II sensitivity") @@ -635,14 +636,17 @@ def add_Phase_II_exposure_sens_line(self, sens): sens.CRLB_constant = 180 standard_exposure = sens.effective_volume*sens.Experiment.livetime/m**3/year + #sens.sensitivity() + #sens.CL90(Experiment={"number_density": 2.09e17/m**3})/eV + sens.print_systematics() sens.print_statistics() - sens.sensitivity() sens.print_Efficiencies() sens.print_SNRs() + logger.info("Phase II sensitivity for exposure {} calculated: {}".format(standard_exposure, sens.sensitivity()/eV**2)) - # Phase II experimental results + # Phase II experimental results from frequentist analysis phaseIIsens = 9822 phaseIIsense_error = 1520 exposure_error = np.sqrt((standard_exposure*0.008)**2 + (standard_exposure*0.09)**2) @@ -652,18 +656,19 @@ def add_Phase_II_exposure_sens_line(self, sens): label="Phase II (measured)") - limits = [] + sigma_mbetas = [] years = [] for ex in self.exposures: lt = ex/sens.effective_volume years.append(lt/year) sens.Experiment.livetime = lt - limits.append(sens.sensitivity()/eV**2) + sigma_mbetas.append(sens.sensitivity()/eV**2) #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) - unit = r"m$^{-3}$" - gas = r"T$_2$" - self.ax.plot(self.exposures/m**3/year, limits, color='k', linestyle=':')#, label="{} density = {:.1e} {}".format(gas, 7.5e16, unit)) + + # unit = r"m$^{-3}$" + # gas = r"T$_2$" + self.ax.plot(self.exposures/m**3/year, sigma_mbetas, color='k', linestyle=':')#, label="{} density = {:.1e} {}".format(gas, 7.5e16, unit)) def get_relative(val, axis): xmin, xmax = self.ax.get_xlim() if axis == "x" else self.ax.get_ylim() diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index e3730f9c..52b9907b 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -149,7 +149,7 @@ def EffectiveVolume(self): self.effective_volume*=self.Experiment.sri_factor # for parent SignalRate function - self.Experiment.v_eff = self.effective_volume + # self.Experiment.v_eff = self.effective_volume return self.effective_volume @@ -298,6 +298,7 @@ def syst_frequency_extraction(self): # non constant slope self.sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + if self.CRLB_constant > 10: sigma_f_CRLB = self.sigma_f_CRLB_slope_fitted """ CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 2fb7ee07..e5a7ffea 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -53,7 +53,8 @@ def __init__(self, config_path): pass self.Experiment = NameSpace({opt: eval(self.cfg.get('Experiment', opt)) for opt in self.cfg.options('Experiment')}) - + + # seetings fro molecular or atomic tritium self.tau_tritium = tritium_livetime if self.Experiment.atomic: self.T_mass = tritium_mass_atomic @@ -65,6 +66,10 @@ def __init__(self, config_path): self.Te_crosssection = tritium_electron_crosssection_molecular self.T_endpoint = tritium_endpoint_molecular self.last_1ev_fraction = last_1ev_fraction_molecular + + # effective volume if configured + if hasattr(self.Experiment, "v_eff"): + self.effective_volume = self.Experiment.v_eff # v_eff can be configured in Experiment section self.DopplerBroadening = NameSpace({opt: eval(self.cfg.get('DopplerBroadening', opt)) for opt in self.cfg.options('DopplerBroadening')}) @@ -83,7 +88,7 @@ def __init__(self, config_path): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" - signal_rate = self.Experiment.number_density*self.Experiment.v_eff*self.last_1ev_fraction/self.tau_tritium + signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): avg_n_T_atoms = self.AvgNumTAtomsPerParticle_MolecularExperiment(self.Experiment.gas_fractions, self.Experiment.H2_type_gas_fractions) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index f8d38bc1..0b7fe044 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -41,8 +41,9 @@ "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", + "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg" + ], "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], "comparison_curve_colors": ["blue", "darkred", "red"], #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, From 07ceb6f9ce6a852e0b230c90185de5075ae797bf Mon Sep 17 00:00:00 2001 From: cclaessens Date: Tue, 28 May 2024 16:21:45 -0700 Subject: [PATCH 157/262] added check of current value to detect wrong combination of category and parameter name --- .../SensitivityParameterScanProcessor.py | 8 ++++ test_analysis/Sensitivity_parameter_scan.py | 38 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 1bb393bc..bce1ac94 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -164,6 +164,14 @@ def InternalRun(self): category, param = self.scan_parameter_name.split(".") + # read current value of param + try: + current_value = self.sens_main.__dict__[category].__dict__[param] + logger.info(f"Current value of {param}: {current_value/self.scan_parameter_unit}") + except KeyError as e: + logger.error(f"Parameter {param} not found in {category}") + raise e + self.sens_main.__dict__[category].__dict__[param] = parameter_value read_back = self.sens_main.__dict__[category].__dict__[param] #setattr(self.sens_main, self.scan_parameter_name, parameter_value) diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index e725461b..c69b2c37 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -19,7 +19,7 @@ -# Configuration for Sensitivity vs. density plot +# Configuration for magnetic homogeneity scan sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", @@ -49,10 +49,10 @@ #sens_scan.Run() -# Configuration for Sensitivity vs. density plot +# Configuration for minimum pitch angle scan sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "plot_path": ".", # optional "figsize": (7.0,6), @@ -78,10 +78,10 @@ #sens_scan.Run() -# Configuration for Sensitivity vs. density plot +# Configuration for L over D scan sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "plot_path": ".", # optional "figsize": (7.0,6), @@ -102,6 +102,34 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False } +#sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") +#sens_scan.Configure(sens_config_dict) +#sens_scan.Run() + +# Configuration for efficiency scan +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", + "plot_path": ".", + # optional + "figsize": (7.0,6), + "track_length_axis": True, + "molecular_axis": False, + "atomic_axis": True, + "density_axis": True, + "cavity": True, + "y_limits": [2e-2, 4], + "density_range": [1e12,3e18], + #"density_range": [1e8, 1e12], + "goals": {"Phase IV (0.04 eV)": 0.04}, + "scan_parameter_name": "Efficiency.radial_efficiency", + "scan_parameter_range": [0.5, 0.9], + "scan_parameter_steps": 3, + "scan_parameter_unit": 1, + "plot_sensitivity_scan_on_log_scale": False, + "goals_x_position": 1.2e12, #0.0002 + "plot_key_parameters": False + } sens_scan = SensitivityParameterScanProcessor("sensitivity_curve_processor") sens_scan.Configure(sens_config_dict) sens_scan.Run() From 41966af435942cae7e021db5d7439e840f6a910b Mon Sep 17 00:00:00 2001 From: Talia Weiss Date: Thu, 6 Jun 2024 11:14:35 -0400 Subject: [PATCH 158/262] Small edits to documentation --- documentation/sensitivity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sensitivity.rst b/documentation/sensitivity.rst index e5f1c301..5d54b159 100644 --- a/documentation/sensitivity.rst +++ b/documentation/sensitivity.rst @@ -66,7 +66,7 @@ This is done in the ``DopplerBroadening`` section of the configuration file, by - For a molecular tritium experiment, you need to input a number ``fraction_uncertainty_on_doppler_broadening`` (which equals ``delta_trans``/``sigma_trans``) in the ``DopplerBroadening`` section of the configuration file. Calculation of ``sigma_trans`` for option 2: -For a thermalized source gas, the translational Doppler broadening is described by Gaussian with standard deviation +For a thermalized source gas, the translational Doppler broadening is described by a Gaussian with standard deviation :math:`{\sigma_{\text{trans}} = \sqrt{\frac{p_{\text{rec}}^2}{2m_T}2 k_B T}}`, where :math:`{m_T}` is the mass of tritium and :math:`{T}` is the gas temperature. From 26fc765efa5641194cd33daa5c634e64f60a128d Mon Sep 17 00:00:00 2001 From: cclaessens Date: Fri, 7 Jun 2024 16:12:07 -0700 Subject: [PATCH 159/262] changed 1.28 to 1.64 --- .../CavitySensitivityCurveProcessor.py | 46 +++++++++++++------ mermithid/sensitivity/SensitivityFormulas.py | 3 +- test_analysis/Cavity_Sensitivity_analysis.py | 39 ++++++++-------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 453731b4..eefc923d 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -63,6 +63,7 @@ def InternalConfigure(self, params): self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") self.comparison_curve_label = reader.read_param(params, 'comparison_curve_label', r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) + self.main_curve_color = reader.read_param(params, 'main_curve_color', "darkblue") # options self.comparison_curve = reader.read_param(params, 'comparison_curve', False) @@ -261,7 +262,7 @@ def InternalRun(self): if self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color='darkblue', label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') # add line for comparison using second config @@ -384,7 +385,8 @@ def create_plot(self): logger.info("Adding frequency axis") ax.set_xlim(self.frequencies[0]/Hz, self.frequencies[-1]/Hz) ax.set_xlabel("TE011 frequency (Hz)") - ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + #ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + ax.set_ylim((np.array(self.ylim)**2/1.64)) ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") self.ax2 = ax.twinx() @@ -400,7 +402,8 @@ def create_plot(self): axis_label = r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" ax.set_xlabel(axis_label) - ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + #ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) + ax.set_ylim((np.array(self.ylim)**2/1.64)) ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") self.ax2 = ax.twinx() @@ -449,15 +452,23 @@ def create_plot(self): def add_track_length_axis(self): if self.atomic_axis: - N_ref = len(self.sens_ref) ax2 = self.ax.twiny() ax2.set_xscale("log") ax2.set_xlabel("(Atomic) track length (s)") - ax2.set_xlim(self.sens_ref[N_ref - 1].track_length(self.rhos[0])/s, - self.sens_ref[N_ref - 1].track_length(self.rhos[-1])/s) + + if self.sens_main_is_atomic: + ax2.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) + else: + for sens in self.sens_ref: + if sens.Experiment.atomic: + ax2.set_xlim(sens.track_length(self.rhos[0])/s, + sens.track_length(self.rhos[-1])/s) if self.molecular_axis: ax3 = self.ax.twiny() + ax3.set_xscale("log") + ax3.set_xlabel("(Molecular) track length (s)") if self.atomic_axis: ax3.spines["top"].set_position(("axes", 1.2)) @@ -466,13 +477,17 @@ def add_track_length_axis(self): for sp in ax3.spines.values(): sp.set_visible(False) ax3.spines["top"].set_visible(True) + + if not self.sens_main_is_atomic: + ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, + self.sens_main.track_length(self.rhos[-1])/s) + else: + for sens in self.sens_ref: + if not sens.Experiment.atomic: + ax3.set_xlim(sens.track_length(self.rhos[0])/s, + sens.track_length(self.rhos[-1])/s) - ax3.set_xscale("log") - ax3.set_xlabel("(Molecular) track length (s)") - ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, - self.sens_main.track_length(self.rhos[-1])/s) - - else: + if not self.molecular_axis and not self.atomic_axis: logger.warning("No track length axis added since neither atomic nor molecular was requested") self.fig.tight_layout() @@ -692,7 +707,7 @@ def get_relative(val, axis): head_width=0.01, head_length=0.01, ) - print(x_start, y_start) + self.ax.annotate("Phase II T$_2$ density \nand resolution", xy=[x_start*0.9, y_start*1.01],textcoords="axes fraction", fontsize=13) def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): @@ -767,7 +782,10 @@ def range(self, start, stop): def save(self, savepath, **kwargs): if self.density_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + if self.track_length_axis: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.85)) + else: + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) elif self.frequency_axis: if self.magnetic_field_axis: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index e5a7ffea..8c4493c9 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -152,7 +152,8 @@ def sensitivity(self, **kwargs): def CL90(self, **kwargs): """ Gives 90% CL upper limit on neutrino mass.""" # 90% of gaussian are contained in +-1.64 sigma region - return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + #return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) + return np.sqrt(1.64*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0b7fe044..2418bfac 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -19,7 +19,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_exposure_curve.pdf", + "plot_path": "./sensitivity_vs_exposure_curve_1.64.pdf", # optional "figsize": (10,6), "fontsize": 15, @@ -38,7 +38,7 @@ "exposure_range": [1e-11, 1e4], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "goals": {"Phase III (0.2 eV)": (0.2**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", @@ -55,9 +55,9 @@ "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4 } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() # Configuration for Sensitivity vs. frequency plot @@ -85,7 +85,7 @@ "density_range": [1e7, 1e20], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", @@ -149,29 +149,32 @@ # Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_density_curve.pdf", + #"config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_density_curve_1.64.pdf", # optional "figsize": (7.0,6), + "fontsize": 15, "track_length_axis": True, "molecular_axis": False, "atomic_axis": True, "density_axis": True, "cavity": True, "y_limits": [2e-2, 4], - "density_range": [1e12,3e18], + "density_range": [1e12,1e20], "efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": [#"Molecular, reaching target", - "Atomic, alternative scenario 1"], - #"Atomic, reaching target"], + "main_curve_upper_label": f"Phase III with 500 MHz cavity", #r"Molecular"+"\n"+"Reaching target", + "main_curve_color": "darkred", + "comparison_curve_label": ["Molecular, reaching target", + "Phase IV with 150 MHz cavities", + "Atomic, reaching target"], # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, + "goals": {"Phase IV target (0.04 eV)": 0.04}, "comparison_curve": True, "comparison_curve_colors": ["red"], "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, + "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #, #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], @@ -182,9 +185,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() # Configuration for Sensitivity vs. density plot for best possible molecular scenario From 775e356f6cdb7196321b16e1945f4bc1fc8a8b58 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 10 Jun 2024 17:28:03 -0400 Subject: [PATCH 160/262] Temporarily modify sensitivity test file not to include comparison curves --- test_analysis/LFA_Sensitivity.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index f00e4b99..47d21d66 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -62,7 +62,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_sensitivity_vs_exposure_curve_1GHz.pdf", + "plot_path": "./lfa_sensitivity_vs_exposure_curve_PhaseIV.pdf", "exposure_axis": True, # optional "figsize": (10,6), @@ -77,12 +77,13 @@ "y_limits": [10e-3, 500], "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], - "main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", - "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", + #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", + "main_curve_upper_label": r"Phase IV scenario$\,-\,$150 MHz", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.4 eV)": (0.4**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, - "comparison_curve": True, + "comparison_curve": False, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], + "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], @@ -96,8 +97,8 @@ "label_x_position": 0.015, #1.5e15, #0.02, #1e14, "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4, - "add_PhaseII": True, - "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg" + "add_PhaseII": False + #"PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg" } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From 185459e25ed93791373def69726aa0fb6d00fe84 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 11 Jun 2024 11:58:06 -0400 Subject: [PATCH 161/262] Ensure I'm optimizing over density --- test_analysis/LFA_Sensitivity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index 47d21d66..66194cd9 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -65,6 +65,7 @@ "plot_path": "./lfa_sensitivity_vs_exposure_curve_PhaseIV.pdf", "exposure_axis": True, # optional + "optimize_main_density": True, "figsize": (10,6), "fontsize": 15, "legend_location": "upper right", @@ -81,9 +82,9 @@ "main_curve_upper_label": r"Phase IV scenario$\,-\,$150 MHz", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.4 eV)": (0.4**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, - "comparison_curve": False, + "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", - "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], From d462bf94abe701fa456ed942a6e8a955fc21ddd3 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 11 Jun 2024 12:00:54 -0400 Subject: [PATCH 162/262] Remove optimize_main_density parameter --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index ef126fdf..65d01b34 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -67,7 +67,7 @@ def InternalConfigure(self, params): self.main_curve_color = reader.read_param(params, 'main_curve_color', "darkblue") # options - self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) + #self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) self.optimize_comparison_density = reader.read_param(params, 'optimize_comparison_density', True) self.comparison_curve = reader.read_param(params, 'comparison_curve', False) self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) @@ -238,11 +238,10 @@ def InternalRun(self): self.add_goal(value, key) # optimize density - if self.optimize_main_density: - limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt = np.argmin(limit) - rho_opt = self.rhos[opt] - self.sens_main.Experiment.number_density = rho_opt + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + self.sens_main.Experiment.number_density = rho_opt # if B is list plot line for each B if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): From 5ecb40ff0555f0353fbf0646e93f4ec3e5952353 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 11 Jun 2024 12:35:24 -0400 Subject: [PATCH 163/262] No longer configure unused parameter --- test_analysis/LFA_Sensitivity.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index 66194cd9..c48e655d 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -65,7 +65,6 @@ "plot_path": "./lfa_sensitivity_vs_exposure_curve_PhaseIV.pdf", "exposure_axis": True, # optional - "optimize_main_density": True, "figsize": (10,6), "fontsize": 15, "legend_location": "upper right", @@ -79,9 +78,9 @@ "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", - "main_curve_upper_label": r"Phase IV scenario$\,-\,$150 MHz", + "main_curve_upper_label": r"Phase III scenario$\,-\,$1 GHz", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", - "goals": {"Phase III (0.4 eV)": (0.4**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "goals": {"Phase III (0.4 eV)": (0.4**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], @@ -89,9 +88,9 @@ # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_label": [r"LFA (atomic T)$\,-\,$500 MHz", r"Phase IV scenario$\,-\,$150 MHz"], + "comparison_curve_label": [r"Phase III scenario$\,-\,$500 MHz", r"Phase IV scenario$\,-\,$150 MHz"], #"comparison_curve_colors": ["blue", "darkred", "red"], - "comparison_curve_colors": ["darkred", "red"], + "comparison_curve_colors": ["blue", "red"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, From f3ce3e92ced35a835d513919c3423b0f7a947d6b Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 11 Jun 2024 21:14:15 -0400 Subject: [PATCH 164/262] Coded calculation of atom trap bite efficiency; added efficiency factor from axial frequency cut --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 1 + mermithid/sensitivity/SensitivityCavityFormulas.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 65d01b34..9db3baf4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -337,6 +337,7 @@ def InternalRun(self): logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) self.sens_ref[i].print_SNRs(rho_opt_ref) + self.sens_ref[i].print_Efficiencies() logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index fa5a65a3..f6d766b2 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -143,8 +143,9 @@ def EffectiveVolume(self): #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - - self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() + self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 + self.fa_cut_efficiency = np.cos(self.Efficiency.min_pitch_used_in_analysis)/self.PitchDependentTrappingEfficiency() + self.effective_volume = self.total_volume*self.radial_efficiency*self.Efficiency.detection_efficiency*self.fa_cut_efficiency*self.PitchDependentTrappingEfficiency() #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor @@ -438,8 +439,9 @@ def print_Efficiencies(self): if not self.Efficiency.usefixedvalue: # radial and detection efficiency are configured in the config file - logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) + logger.info("Radial efficiency: {}".format(self.radial_efficiency)) logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Efficiency from axial frquency cut: {}".format(self.fa_cut_efficiency)) logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) From 9242e40800bd437b4e7c32073100dbc68f9e9057 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 10 Jul 2024 11:06:22 -0400 Subject: [PATCH 165/262] Add initial 'BNL magnet' sensitivity scenario --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 ++ test_analysis/LFA_Sensitivity.py | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index f6d766b2..7aa72628 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -143,9 +143,11 @@ def EffectiveVolume(self): #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) + self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 self.fa_cut_efficiency = np.cos(self.Efficiency.min_pitch_used_in_analysis)/self.PitchDependentTrappingEfficiency() self.effective_volume = self.total_volume*self.radial_efficiency*self.Efficiency.detection_efficiency*self.fa_cut_efficiency*self.PitchDependentTrappingEfficiency() + #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index c48e655d..ba905e66 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -62,7 +62,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_sensitivity_vs_exposure_curve_PhaseIV.pdf", + "plot_path": "./lfa_with_BNL_constraints_sensitivity_vs_exposure_curve_PhaseIV.pdf", "exposure_axis": True, # optional "figsize": (10,6), @@ -78,19 +78,20 @@ "density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", - "main_curve_upper_label": r"Phase III scenario$\,-\,$1 GHz", + "main_curve_upper_label": r"Phase III scenario: 1 GHz, L/D=9", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.4 eV)": (0.4**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", - "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], + "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg"], + # "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", # "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], #"comparison_curve_label": [r"Molecular, conservative", "Atomic, conservative", "Atomic, reaching PIV target"], - "comparison_curve_label": [r"Phase III scenario$\,-\,$500 MHz", r"Phase IV scenario$\,-\,$150 MHz"], + "comparison_curve_label": [r"Phase III scenario: 500 MHz, L/D=5", r"Phase III scenario: 560 MHz, L/D=8"], #r"Phase IV scenario: 150 MHz, L/D=5"], #"comparison_curve_colors": ["blue", "darkred", "red"], - "comparison_curve_colors": ["blue", "red"], + "comparison_curve_colors": ["blue", "darkviolet"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, From ca5f9ed6d689e9c0178dc2faa43761dbeb6a59d9 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Wed, 17 Jul 2024 11:10:57 -0700 Subject: [PATCH 166/262] Added option for log param array to parameter scanning, defaults to 'lin', set to 'log' for logarithmic --- .../SensitivityParameterScanProcessor.py | 18 +++++++++++++++--- test_analysis/Sensitivity_parameter_scan.py | 10 ++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index eb92d42b..07b36604 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -71,6 +71,7 @@ def InternalConfigure(self, params): self.scan_parameter_name = reader.read_param(params, 'scan_parameter_name', 'MagneticField.sigmae_r') self.scan_parameter_range = reader.read_param(params, "scan_parameter_range", [0.1, 2.1]) self.scan_parameter_steps = reader.read_param(params, "scan_parameter_steps", 3) + self.scan_parameter_scale = reader.read_param(params, "scan_parameter_scale", "lin") scan_parameter_unit = reader.read_param(params, "scan_parameter_unit", eV) @@ -136,8 +137,13 @@ def InternalConfigure(self, params): # densities, exposures, runtimes self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 - self.scan_parameter_values = np.linspace(self.scan_parameter_range[0], self.scan_parameter_range[1], self.scan_parameter_steps)*self.scan_parameter_unit - + if(self.scan_parameter_scale == "lin"): + self.scan_parameter_values = np.linspace(self.scan_parameter_range[0], self.scan_parameter_range[1], self.scan_parameter_steps)*self.scan_parameter_unit + elif(self.scan_parameter_scale == "log"): + self.scan_parameter_values = np.logspace(np.log10(self.scan_parameter_range[0]), np.log10(self.scan_parameter_range[1]), self.scan_parameter_steps)*self.scan_parameter_unit + else: + logger.warn("Unexpected parameter scale, assuming linear") + self.scan_parameter_values = np.linspace(self.scan_parameter_range[0], self.scan_parameter_range[1], self.scan_parameter_steps)*self.scan_parameter_unit return True @@ -271,7 +277,13 @@ def InternalRun(self): "Noise Temperatures/K": np.array(self.noise_temp), "SNRs 1eV from temperature": np.array(self.SNR), "track durations": np.array(self.track_duration), "Systematic limits": np.array(self.sys_lim), "Total Sigmas": np.array(self.total_sigma)} - + + results_array = [np.array(self.scan_parameter_values/self.scan_parameter_unit),np.array(self.noise_temp),np.array(self.SNR), + np.array(self.optimum_rhos)*(m**3),np.array(self.track_duration),np.array(self.total_sigma), + 1000*np.array(self.optimum_limits)/eV,np.array(self.sys_lim)] + fmt_array = '%.2f','%.3f','%.1f','%.2E','%.2f','%.1f','%.1f','%.1f' + header_string = 'Param Value, Noise temperature /K, SNR, Gas Density /m3, Track Length /ms, Resolution, Sensitivity /meV, Systematic Limit /meV' + np.savetxt("results_array_{}.csv".format(param),np.array(results_array).T,delimiter=',',fmt=fmt_array,header=header_string) logger.info("Scan parameter: {}".format(self.scan_parameter_name)) logger.info("Tested parameter values: {}".format(self.scan_parameter_values/self.scan_parameter_unit)) logger.info("Best limits: {}".format(np.array(self.optimum_limits)/eV)) diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index 83ebf9c1..06228bce 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -125,6 +125,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", + #"config_file_path": "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg", "plot_path": ".", # optional "figsize": (7.0,6), @@ -137,10 +138,11 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "Efficiency.radial_efficiency", - "scan_parameter_range": [0.5, 0.9], - "scan_parameter_steps": 3, - "scan_parameter_unit": 1, + "scan_parameter_name": "MagneticField.nominal_field", + "scan_parameter_range": [0.0015,0.0095], + "scan_parameter_steps": 5, + "scan_parameter_scale": "lin", + "scan_parameter_unit": T, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False From 297e2d4d468fecdfcb25002e8e4437ac6480348e Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 16 Sep 2024 16:29:42 -0400 Subject: [PATCH 167/262] Printing text to note when numbers are for highest-exposure point on curve --- .../CavitySensitivityCurveProcessor.py | 4 +++ test_analysis/Cavity_Sensitivity_analysis.py | 30 +++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 453731b4..5c55d55a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -304,6 +304,8 @@ def InternalRun(self): self.sens_main.print_Efficiencies() self.sens_main.print_SNRs(rho_opt) + if self.exposure_axis: + logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* @@ -336,6 +338,8 @@ def InternalRun(self): logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) self.sens_ref[i].print_SNRs(rho_opt_ref) + if self.exposure_axis: + logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0b7fe044..dfff103e 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -18,8 +18,8 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_exposure_curve.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_exposure_curve_PhaseIV_87deg_min_pitch.pdf", # optional "figsize": (10,6), "fontsize": 15, @@ -34,12 +34,12 @@ "add_1year_1cav_point_to_last_ref": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "y_limits": [10e-3, 500], - "density_range": [1e12,1e19], + "density_range": [0, 1e19], "exposure_range": [1e-11, 1e4], - "main_curve_upper_label": r"Molecular, conservative", - "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, - "comparison_curve": True, + "main_curve_upper_label": r"Phase IV scenario", #"Molecular, conservative", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "goals": {"Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, #"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), + "comparison_curve": False, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg" @@ -48,7 +48,7 @@ "comparison_curve_colors": ["blue", "darkred", "red"], #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, - "sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction + #"sigmae_theta_r": 0.159, #in eV, energy broadening from theta and r reconstruction "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.015, #1.5e15, #0.02, #1e14, @@ -149,26 +149,26 @@ # Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_density_curve.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_density_curve_87deg_min_pitch.pdf", # optional "figsize": (7.0,6), - "track_length_axis": True, + "track_length_axis": False, "molecular_axis": False, "atomic_axis": True, "density_axis": True, "cavity": True, "y_limits": [2e-2, 4], "density_range": [1e12,3e18], - "efficiency_range": [0.0001, 1], + #"efficiency_range": [0.0001, 1], #"density_range": [1e8, 1e12], - "main_curve_upper_label": r"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", + "main_curve_upper_label": r'Phase IV scenario',#"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", "comparison_curve_label": [#"Molecular, reaching target", "Atomic, alternative scenario 1"], #"Atomic, reaching target"], # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Pilot T goal (0.1 eV)": 0.1, "Phase IV (0.04 eV)": 0.04}, - "comparison_curve": True, + "goals": {"Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "comparison_curve": False, "comparison_curve_colors": ["red"], "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #, From 06f395869658b00f14ca8f00e54536035c51c296 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 16 Sep 2024 17:24:36 -0400 Subject: [PATCH 168/262] Added function to print disclaimers about sensitivity calculation (and option to turn off this printing) --- .../CavitySensitivityCurveProcessor.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 5c55d55a..c685f6ad 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -57,7 +57,6 @@ def InternalConfigure(self, params): self.plot_path = reader.read_param(params, 'plot_path', "required") self.PhaseII_path = reader.read_param(params, 'PhaseII_config_path', '') - # labels self.main_curve_upper_label = reader.read_param(params, 'main_curve_upper_label', r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") @@ -65,6 +64,7 @@ def InternalConfigure(self, params): self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) # options + self.verbose = reader.read_param(params, 'verbose', True) self.comparison_curve = reader.read_param(params, 'comparison_curve', False) self.B_error = reader.read_param(params, 'B_inhomogeneity', 7e-6) self.B_error_uncertainty = reader.read_param(params, 'B_inhom_uncertainty', 0.05) @@ -270,8 +270,6 @@ def InternalRun(self): #self.add_arrow(self.sens_main) - - # PRINT OPTIMUM RESULTS # print number of events @@ -356,9 +354,36 @@ def InternalRun(self): # save plot self.save(self.plot_path) + if self.verbose: + self.print_disclaimers() + return True + def print_disclaimers(): + logger.info("Disclaimers / assumptions:") + logger.info("1. Often, in practice, 'sigmae_r' is used to stand-in for combined radial, \ + azimuthal, and temporal magnetic field spectral broadening effects. Check \ + your experiment config file *and* your processor config dictionary to see \ + if that is the case.") + logger.info("2. Trap design is not yet linked to cavity L/D in the sensitivity model. So, \ + the model does *not* capture how reducing L/D worsens the resolution.") + logger.info("3. In reality, the frequency resolution could be worse or somewhat better \ + than predicted by the general CRLB calculation used here. See work by Florian.") + logger.info("4. The analytic sensitivity formula oaccounts for energy resolution contributions \ + that are *normally distributed*. (Energy resolution = std of the response fn \ + that broadens the spectrum.) To account for asymmetric contributions, generate \ + spectra with MC sampling and then analyze them. This can be done in mermithid.") + logger.info("5. The best-fit mbeta is assumed to be zero when converting to a 90\% limit.") + if self.density_axis: + logger.info("6. This sensitivity formula does not work for very small numbers of counts, \ + because the analytic formula assumes Gaussian statistics. In typical Phase IV \ + scenarios, if the minimum allowed density is 1e-20 atoms/m^3, the optimization \ + over density still works.") + logger.info("Once you have read these disclaimers and are familiar with them, you can set \ + verbose==False in your config dictionary to stop seeing them.") + + def create_plot(self): # setup axis plt.rcParams.update({'font.size': self.fontsize}) From 8a7b0c210fad6fe551edb1dbb04a1bfee5462ba3 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 16 Sep 2024 17:25:32 -0400 Subject: [PATCH 169/262] Fixed mistake in function to print disclaimers about sensitivity calc --- .../processors/Sensitivity/CavitySensitivityCurveProcessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index c685f6ad..887acf99 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -360,7 +360,7 @@ def InternalRun(self): return True - def print_disclaimers(): + def print_disclaimers(self): logger.info("Disclaimers / assumptions:") logger.info("1. Often, in practice, 'sigmae_r' is used to stand-in for combined radial, \ azimuthal, and temporal magnetic field spectral broadening effects. Check \ From f9282deb8d42a710cce27f82850d810ef6103f5e Mon Sep 17 00:00:00 2001 From: benanator77 Date: Mon, 16 Sep 2024 16:56:56 -0700 Subject: [PATCH 170/262] Adding my current version of the parameter scan config --- test_analysis/Sensitivity_parameter_scan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index 06228bce..17d9eeff 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -138,11 +138,11 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "MagneticField.nominal_field", - "scan_parameter_range": [0.0015,0.0095], + "scan_parameter_name": "MagneticField.sigmae_r", + "scan_parameter_range": [.02,0.18], "scan_parameter_steps": 5, "scan_parameter_scale": "lin", - "scan_parameter_unit": T, + "scan_parameter_unit": eV, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": False From e87c9b25a59ea80c9b0b6cee50ce9d9f8a8c5d9a Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 17 Sep 2024 16:38:06 -0700 Subject: [PATCH 171/262] Implemented Talia's suggestions in PR request comments, and also found and fixed a couple of instances of sqrt(sqrt(1.64)) in mermithid/sensitivity/SensitivityFormulas.py --- .../Sensitivity/AnalyticSensitivityEstimation.py | 8 +++++--- .../Sensitivity/CavitySensitivityCurveProcessor.py | 2 +- .../Sensitivity/ConstantSensitivityParameterPlots.py | 12 ++++++------ mermithid/sensitivity/SensitivityFormulas.py | 6 +++--- tests/Sensitivity_test.py | 4 ++-- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py index c82b3b51..8e129e2f 100644 --- a/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py +++ b/mermithid/processors/Sensitivity/AnalyticSensitivityEstimation.py @@ -107,10 +107,12 @@ def CL90(self, **kwargs): # If integrate gaussian -infinity to 1.28*sigma, the result is 90%. # https://www.wolframalpha.com/input?i=1%2F2+%281+%2B+erf%281.281551%2Fsqrt%282%29%29%29 # So we assume that 1.28*sigma_{m_beta^2} is the 90% upper limit on m_beta^2. - return np.sqrt(1.281551*self.sensitivity(**kwargs)) + # Note: 1.64 update, to prevent gaining from a sensitivity from a negative mass result + ### We now use 1.64 = 1.28**2 + return np.sqrt(1.281551**2*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(1.281551*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + return np.sqrt(1.281551**2*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # print functions @@ -122,4 +124,4 @@ def print_systematics(self): print() for label, sigma, delta in zip(labels, sigmas, deltas): - print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") \ No newline at end of file + print(label, " "*(np.max([len(l) for l in labels])-len(label)), "%8.2f"%(sigma/meV), "+/-", "%8.2f"%(delta/meV), "meV") diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index eefc923d..e46e0e26 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -607,7 +607,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) - logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.28*limit))) + logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.64*limit))) sens.print_statistics() sens.print_systematics() diff --git a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py index 18aab05d..cbd26cef 100644 --- a/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py +++ b/mermithid/processors/Sensitivity/ConstantSensitivityParameterPlots.py @@ -56,7 +56,7 @@ def InternalConfigure(self, params): ''' # file paths self.config_file_path = reader.read_param(params, 'config_file_path', "required") - self.sensitivity_target = reader.read_param(params, 'sensitivity_target', [0.4**2/np.sqrt(1.64), 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] ) + self.sensitivity_target = reader.read_param(params, 'sensitivity_target', [0.4**2/1.64, 0.7**2/1.64, 1**2/1.64] ) self.initialInhomogeneity = reader.read_param(params, 'initial_Inhomogeneity', 1e-8) self.veff_range = reader.read_param(params, 'veff_range', [0.001, 1]) @@ -138,7 +138,7 @@ def InternalRun(self): - index.append(np.where((self.CLs[j]/eVself.Berrors[1]))) + index.append(np.where((self.CLs[j]/eVself.Berrors[1]))) logger.info('Achieved 90CL limits: {}'.format(self.CLs[j]/eV)) time.sleep(1) @@ -148,7 +148,7 @@ def InternalRun(self): plt.subplot(121) for j in range(len(self.sensitivity_target)): - plt.plot(self.veffs[index[j]]/(m**3), self.needed_Bs[j][index[j]], label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.plot(self.veffs[index[j]]/(m**3), self.needed_Bs[j][index[j]], label='90% CL = {} eV'.format(np.round(np.sqrt(1.64*self.sensitivity_target[j]),1))) #plt.scatter(self.veffs/(m**3), self.needed_Bs, c=self.CLs/eV, marker='.') #plt.colorbar() plt.xlabel('Effective Volume (m³)') @@ -161,7 +161,7 @@ def InternalRun(self): plt.subplot(122) for j in range(len(self.sensitivity_target)): - plt.plot(self.veffs[index[j]]/(m**3), self.rho_opts[j][index[j]]*m**3, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.plot(self.veffs[index[j]]/(m**3), self.rho_opts[j][index[j]]*m**3, label='90% CL = {} eV'.format(np.round(np.sqrt(1.64*self.sensitivity_target[j]),1))) plt.xlabel('Effective Volume (m³)') plt.ylabel('Optimum number density (1/m³)') plt.xscale('log') @@ -176,7 +176,7 @@ def InternalRun(self): plt.subplot(121) for j in range(len(self.sensitivity_target)): - plt.plot(self.veffs[index[j]]/(m**3), self.needed_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.plot(self.veffs[index[j]]/(m**3), self.needed_res[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(1.64*self.sensitivity_target[j]),1))) plt.xlabel('Effective Volume (m³)') plt.ylabel('Total energy resolution (eV)') plt.xscale('log') @@ -186,7 +186,7 @@ def InternalRun(self): plt.subplot(122) for j in range(len(self.sensitivity_target)): - plt.plot(self.veffs[index[j]]/(m**3), self.needed_res_sigma[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(np.sqrt(1.64)*self.sensitivity_target[j]),1))) + plt.plot(self.veffs[index[j]]/(m**3), self.needed_res_sigma[j][index[j]]/eV, label='90% CL = {} eV'.format(np.round(np.sqrt(1.64*self.sensitivity_target[j]),1))) plt.xlabel('Effective Volume (m³)') plt.ylabel('Uncertainty on total energy resolution (eV)') plt.xscale('log') diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 8c4493c9..fba02797 100644 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -156,7 +156,7 @@ def CL90(self, **kwargs): return np.sqrt(1.64*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(np.sqrt(1.64)*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + return np.sqrt(1.64*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # PHYSICS Functions @@ -225,7 +225,7 @@ def get_systematics(self): def print_statistics(self): print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") + print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() @@ -242,7 +242,7 @@ def print_systematics(self): except AttributeError: pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") - print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") + print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.SystSens())/meV), "meV") def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index 5554f87e..e75500e5 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -41,7 +41,7 @@ def test_SensitivityCurveProcessor(self): "exposure_range": [1e-11, 1e4], "main_curve_upper_label": r"Molecular, conservative", "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase III (0.2 eV)": (0.2**2/np.sqrt(1.64)), "Phase IV (0.04 eV)": (0.04**2/np.sqrt(1.64))}, + "goals": {"Phase III (0.2 eV)": (0.2**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", @@ -125,7 +125,7 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", - "sensitivity_target": [0.4**2/np.sqrt(1.64)]#, 0.7**2/np.sqrt(1.64), 1**2/np.sqrt(1.64)] + "sensitivity_target": [0.4**2/1.64]#, 0.7**2/1.64, 1**2/1.64] } #sens = ConstantSensitivityParameterPlots("sensitivity_processor") #sens.Configure(sens_config_dict) From d8679f9b947bd3df752ccbfff83c940776abdb1c Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sat, 5 Oct 2024 19:35:13 -0500 Subject: [PATCH 172/262] Making plot with updated LFA values and PIV scenario with min pitch = 87deg --- test_analysis/Cavity_Sensitivity_analysis.py | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0cef6029..cd2346d6 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -183,6 +183,48 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_with_BNL_constraints_sensitivity_vs_exposure_curve_PhaseIII-and-IV.pdf", + "exposure_axis": True, + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "cavity": True, + "add_PhaseII": True, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "add_1year_1cav_point_to_last_ref": False, + "y_limits": [10e-3, 500], + "density_range": [1e12,1e19], + "exposure_range": [1e-11, 1e4], + #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", + "main_curve_upper_label": r"Phase III scenario: 1 GHz", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", + "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", + "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], + #"comparison_curve_colors": ["blue", "darkred", "red"], + "comparison_curve_colors": ["blue", "red"], + "optimize_main_density": False, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4, + } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) sens_curve.Run() From edc49f8c3919e68918e17a1afb8ab30e61bd642b Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 22 Oct 2024 11:30:06 -0500 Subject: [PATCH 173/262] Print out systematics and track length before optimizing density; start making sensitivity vs livetime plot --- .../CavitySensitivityCurveProcessor.py | 91 ++++++++++++------- test_analysis/Cavity_Sensitivity_analysis.py | 89 ++++++++++++++++-- test_analysis/Sensitivity_parameter_scan.py | 6 +- 3 files changed, 143 insertions(+), 43 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index b8198ab1..3d54c356 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -1,8 +1,9 @@ ''' -Calculate sensitivity curve and plot vs. pressure +Calculate sensitivity curve and plot vs. number density or exposure. function. Author: C. Claessens, T. Weiss -Date:06/07/2023 +Date: 06/07/2023 +Updated: 10/20/2024 More description ''' @@ -80,13 +81,14 @@ def InternalConfigure(self, params): self.density_axis = reader.read_param(params, "density_axis", True) self.frequency_axis = reader.read_param(params, "frequency_axis", False) self.exposure_axis = reader.read_param(params, "exposure_axis", False) + self.livetime_axis = reader.read_param(params, "livetime_axis", False) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) self.magnetic_field_axis = reader.read_param(params, 'magnetic_field_axis', False) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) - self.year_range = reader.read_param(params, "years_range", [0.1, 10]) + self.year_range = reader.read_param(params, "years_range", [0.1, 20]) self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) @@ -111,13 +113,16 @@ def InternalConfigure(self, params): if self.density_axis: self.add_sens_line = self.add_density_sens_line - logger.info("Doing density lines") + logger.info("Plotting sensitivity vs. density") elif self.frequency_axis: self.add_sens_line = self.add_frequency_sens_line - logger.info("Doing frequency lines") + logger.info("Plotting sensitivity vs. frequency") elif self.exposure_axis: self.add_sens_line = self.add_exposure_sens_line - logger.info("Doing exposure lines") + logger.info("Plotting sensitivity vs. exposure") + elif self.livetime_axis: + self.add_sens_line = self.add_livetime_sens_line + logger.info("Plotting sensitivity vs. livetime") else: raise ValueError("No axis specified") @@ -178,8 +183,10 @@ def InternalConfigure(self, params): def InternalRun(self): - - + logger.info("Systematics before density optimization:") + self.sens_main.print_systematics() + logger.info("Number density: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) + logger.info("Corresponding track length: {} s".format(self.sens_main.track_length(self.sens_main.Experiment.number_density)/s)) if self.make_key_parameter_plots: @@ -303,7 +310,7 @@ def InternalRun(self): self.sens_main.print_Efficiencies() self.sens_main.print_SNRs(rho_opt) - if self.exposure_axis: + if self.exposure_axis or self.livetime_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) @@ -337,7 +344,7 @@ def InternalRun(self): logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) self.sens_ref[i].print_SNRs(rho_opt_ref) - if self.exposure_axis: + if self.exposure_axis or self.livetime_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) @@ -423,7 +430,7 @@ def create_plot(self): self.ax2.set_yscale("log") self.ax2.set_ylim(self.ylim) - else: + elif self.exposure_axis: logger.info("Adding exposure axis") ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) #ax.tick_params(axis='x', which='minor', bottom=True) @@ -439,6 +446,20 @@ def create_plot(self): self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") self.ax2.set_yscale("log") self.ax2.set_ylim(self.ylim) + + else: + logger.info("Adding livetime axis") + ax.set_xlim(self.year_range[0], self.year_range[-1]) + axis_label = r"Livetime (years)" + + ax.set_xlabel(axis_label) + ax.set_ylim((np.array(self.ylim)**2/1.64)) + ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + + self.ax2 = ax.twinx() + self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") + self.ax2.set_yscale("log") + self.ax2.set_ylim(self.ylim) if self.make_key_parameter_plots: @@ -633,14 +654,17 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" #sens.Experiment.livetime = lt limit = sens.sensitivity()/eV**2 - - self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) - - logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.64*limit))) + if self.exposure_axis: + self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) + logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.64*limit))) + if self.livetime_axis: + self.ax.scatter([sens.Experiment.livetime/year], [limit], marker="s", s=25, color=color, label=label, zorder=10) + logger.info("Livetime and mass limit for single point: {}, {}".format(sens.Experiment.livetime/year, np.sqrt(1.64*limit))) + sens.print_statistics() sens.print_systematics() - def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): + def add_exposure_sens_line(self, sens, livetime_plot=False, plot_key_params=False, **kwargs): sigma_mbeta = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] opt = np.argmin(sigma_mbeta) @@ -650,27 +674,26 @@ def add_exposure_sens_line(self, sens, plot_key_params=False, **kwargs): logger.info("Optimum density: {} /m^3".format(rho_opt*m**3)) logger.info("Years: {}".format(sens.Experiment.livetime/year)) - standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - - - self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) - sigma_mbetas = [] years = [] - for ex in self.exposures: - lt = ex/sens.EffectiveVolume() - years.append(lt/year) - sens.Experiment.livetime = lt - sigma_mbetas.append(sens.sensitivity()/eV**2) - #exposures.append(sens.EffectiveVolume()/m**3*sens.Experiment.livetime/year) - - """if sens.Experiment.atomic: - gas = "T" + if livetime_plot: + self.ax.scatter([sens.Experiment.livetime/year], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + for lt in self.years: + sens.Experiment.livetime = lt + sigma_mbetas.append(sens.sensitivity()/eV**2) + self.ax.plot(self.years/year, sigma_mbetas, color=kwargs["color"]) else: - gas = r"T$_2$" - unit = r"m$^{-3}$""" - self.ax.plot(self.exposures/m**3/year, sigma_mbetas, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) - + standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year + self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + for ex in self.exposures: + lt = ex/sens.EffectiveVolume() + years.append(lt/year) + sens.Experiment.livetime = lt + sigma_mbetas.append(sens.sensitivity()/eV**2) + self.ax.plot(self.exposures/m**3/year, sigma_mbetas, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) + + + def add_Phase_II_exposure_sens_line(self, sens): logger.warning("Adding Phase II sensitivity") sens.Experiment.number_density = 2.09e17/m**3 diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index cd2346d6..0cfad6f5 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -191,7 +191,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_with_BNL_constraints_sensitivity_vs_exposure_curve_PhaseIII-and-IV.pdf", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Oct-22-2024.pdf", "exposure_axis": True, # optional "figsize": (10,6), @@ -200,13 +200,59 @@ "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, + "exposure_axis": False, + "density_axis": False, + "livetime_axis": True, + "cavity": True, + "add_PhaseII": True, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "add_1year_1cav_point_to_last_ref": False, + "y_limits": [0.1,20], + #"density_range": [1e12,1e19], + "year_range": [1e-11, 1e4], + #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", + "main_curve_upper_label": r"Phase III scenario: 1 GHz", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", + "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", + "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], + #"comparison_curve_colors": ["blue", "darkred", "red"], + "comparison_curve_colors": ["red", "black"], + "optimize_main_density": False, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4, + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + + +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_exposure_curve_Oct-20-2024.pdf", + "exposure_axis": True, + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "exposure_axis": True, "density_axis": False, "cavity": True, "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "add_1year_1cav_point_to_last_ref": False, "y_limits": [10e-3, 500], - "density_range": [1e12,1e19], + #"density_range": [1e12,1e19], "exposure_range": [1e-11, 1e4], #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", "main_curve_upper_label": r"Phase III scenario: 1 GHz", @@ -217,7 +263,7 @@ "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], #"comparison_curve_colors": ["blue", "darkred", "red"], - "comparison_curve_colors": ["blue", "red"], + "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, @@ -225,9 +271,9 @@ "goals_x_position": 0.2e-10, #2e12 #0.0002 "goals_y_rel_position": 0.4, } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() # Configuration for Sensitivity vs. density plot for best possible molecular scenario @@ -366,6 +412,37 @@ #sens_curve.Run() +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", #Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_exposure_curve_CCA.pdf", + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "exposure_axis": True, + "cavity": True, + "add_PhaseII": False, + #"add_1year_1cav_point_to_last_ref": True, + "y_limits": [10e-3, 500], + "density_range": [1e12, 1e19], + "exposure_range": [1e-11, 1e10], + "main_curve_upper_label": r"CCA", #"Molecular, conservative", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "comparison_curve": False, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4 + } +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + diff --git a/test_analysis/Sensitivity_parameter_scan.py b/test_analysis/Sensitivity_parameter_scan.py index c69b2c37..70e033fa 100644 --- a/test_analysis/Sensitivity_parameter_scan.py +++ b/test_analysis/Sensitivity_parameter_scan.py @@ -122,9 +122,9 @@ "density_range": [1e12,3e18], #"density_range": [1e8, 1e12], "goals": {"Phase IV (0.04 eV)": 0.04}, - "scan_parameter_name": "Efficiency.radial_efficiency", - "scan_parameter_range": [0.5, 0.9], - "scan_parameter_steps": 3, + "scan_parameter_name": "Experiment.livetime", + "scan_parameter_range": [0.1*3e7, 20*3e7], + "scan_parameter_steps": 10, "scan_parameter_unit": 1, "plot_sensitivity_scan_on_log_scale": False, "goals_x_position": 1.2e12, #0.0002 From 099727fb7ae0226f88282a7f78f081f38fce51d2 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 30 Oct 2024 19:57:16 -0400 Subject: [PATCH 174/262] Implemented Wouter's 'flat fraction' code in sensitivity calculation, based on my model of trap shapes (see LUCKEY write-up) --- .../CavitySensitivityCurveProcessor.py | 31 +++--- .../sensitivity/SensitivityCavityFormulas.py | 68 +++++++++++--- test_analysis/CCA_Sensitivity.py | 33 +++++++ test_analysis/Cavity_Sensitivity_analysis.py | 94 ++++++------------- 4 files changed, 137 insertions(+), 89 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 3d54c356..a37d9a8b 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -88,7 +88,7 @@ def InternalConfigure(self, params): self.magnetic_field_axis = reader.read_param(params, 'magnetic_field_axis', False) self.density_range = reader.read_param(params, 'density_range', [1e14,1e21]) - self.year_range = reader.read_param(params, "years_range", [0.1, 20]) + self.year_range = reader.read_param(params, "year_range", [0.1, 20]) self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) @@ -117,12 +117,12 @@ def InternalConfigure(self, params): elif self.frequency_axis: self.add_sens_line = self.add_frequency_sens_line logger.info("Plotting sensitivity vs. frequency") - elif self.exposure_axis: + elif self.exposure_axis or self.livetime_axis: self.add_sens_line = self.add_exposure_sens_line - logger.info("Plotting sensitivity vs. exposure") - elif self.livetime_axis: - self.add_sens_line = self.add_livetime_sens_line - logger.info("Plotting sensitivity vs. livetime") + logger.info("Plotting sensitivity vs. exposure or livetime") + #elif self.livetime_axis: + # self.add_sens_line = self.add_exposure_sens_line(livetime_plot=True) + # logger.info("Plotting sensitivity vs. livetime") else: raise ValueError("No axis specified") @@ -255,7 +255,10 @@ def InternalRun(self): #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[a] * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color=color) + if self.livetime_axis: + self.add_sens_line(self.sens_main, livetime_plot=True, color=color) + else: + self.add_sens_line(self.sens_main, color=color) #print("sigmae_theta_r:", self.sens_main.MagneticField.sigmae_r/eV) self.sens_main.print_systematics() self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color="darkblue") @@ -269,7 +272,10 @@ def InternalRun(self): if self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label) + if self.livetime_axis: + self.add_sens_line(self.sens_main, livetime_plot=True, color=self.main_curve_color, label=self.main_curve_upper_label) + else: + self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label) #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') # add line for comparison using second config @@ -435,7 +441,7 @@ def create_plot(self): ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) #ax.tick_params(axis='x', which='minor', bottom=True) #ax.tick_params(axis='y', which='minor', left=True) - axis_label = r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" + axis_label = r"Effective Volume $\times$ Time (m$^3$y)" #r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" ax.set_xlabel(axis_label) #ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) @@ -552,7 +558,10 @@ def add_magnetic_field_axis(self): def add_comparison_curve(self, label, color='k'): for a in range(len(self.sens_ref)): - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) + if self.livetime_axis == True: + limits = self.add_sens_line(self.sens_ref[a], livetime_plot=True, plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) + else: + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) if not self.density_axis and self.add_1year_1cav_point_to_last_ref: @@ -780,7 +789,7 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): magnetic_field = freq/(e/(2*np.pi*me)/gamma) sens.MagneticField.nominal_field = magnetic_field - logger.info("Frequency: {:2e} Hz, Magentic field: {:2e} T".format(freq/Hz, magnetic_field/T)) + logger.info("Frequency: {:2e} Hz, Magnetic field: {:2e} T".format(freq/Hz, magnetic_field/T)) # calcualte new cavity properties sens.CavityRadius() diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 52b9907b..dc060b34 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -27,12 +27,51 @@ def db_to_pwr_ratio(q_db): return 10**(q_db/10) -def axial_frequency(length, kin_energy, max_pitch_angle=86): +def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, kin_energy, flat_fraction=0.5, trajectory = None): + # returns the axial motion frequency and a trajectory of point along the axial motion + # also return the average magnetic field seen by the electron + # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable + # See LUCKEY write-up for a little more on Talia's "flat fraction" trap model + pitch = pitch/180*np.pi + minimum_trapped_pitch/180*np.pi + + # Axial motion: + z_w = cavity_length/2 + speed = beta(kin_energy)*c0 + transverse_speed = speed*np.cos(pitch) + tan_min = np.tan(minimum_trapped_pitch) + # Axial frequency + time_flat = z_w*flat_fraction/transverse_speed + time_harmonic = np.pi*z_w*(1-flat_fraction)*tan_min/(2*speed*np.sin(pitch)) + axial_frequency = 1/4/(time_flat+time_harmonic) + + #Average magnetic field: + magnetic_field_avg_harm = magnetic_field/2*(1+1/np.sin(pitch)**2) + magnetic_field_avg = (magnetic_field_avg_harm*time_harmonic + magnetic_field*time_flat)/(time_harmonic+time_flat) + + # Trajectory: + if trajectory is None: + z_t = None + else: + omega_harm = speed*np.sin(pitch)/z_w/tan_min/(1-flat_fraction) + time = np.linspace(0, time_flat+time_harmonic, trajectory) + z_t = np.heaviside(time_flat-time, 0.5)*time*transverse_speed +\ + np.heaviside(time-time_flat, 0.5)*(z_w*flat_fraction + z_w*(1-flat_fraction)*tan_min/np.tan(pitch)*np.sin(omega_harm*(time-time_flat))) + + return axial_frequency, magnetic_field_avg, z_t + +def magnetic_field_flat_harmonic(z,magnetic_field, cavity_length,minimum_trapped_pitch, flat_fraction=0.5): + z_w = cavity_length/2 + a = z_w*(1-flat_fraction)*np.tan(minimum_trapped_pitch) + return magnetic_field*(1+np.heaviside(np.abs(z)-z_w*flat_fraction, 0.5)*(np.abs(z)-z_w*flat_fraction)**2/a**2) + + +def axial_frequency_box(length, kin_energy, max_pitch_angle=86): pitch_max = max_pitch_angle/180*np.pi return (beta(kin_energy)*c0*np.cos(pitch_max)) / (2*length) def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, max_pitch_angle=86): - # Because of the differenct electron trajectories in the trap, + # Because of the different electron trajectories in the trap, # An electron will see a slightly different magnetic field # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. @@ -178,15 +217,17 @@ def CavityLoadedQ(self): #self.loaded_q =1/(0.22800*((90-self.FrequencyExtraction.minimum_angle_in_bandwidth)*np.pi/180)**2+2**2*0.01076**2/(4*0.22800)) endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) - required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, - self.T_endpoint, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + #required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + # self.T_endpoint, + # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.Experiment.L_over_D*self.CavityRadius()*2, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + required_bw_axialfrequency = max_ax_freq self.required_bw_axialfrequency = required_bw_axialfrequency - - required_bw_meanfield = mean_field_frequency_variation(endpoint_frequency, - self.Experiment.L_over_D, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) - + required_bw_meanfield = required_bw_meanfield = np.abs(frequency(self.T_endpoint, mean_field) - endpoint_frequency) required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting self.required_bw = required_bw @@ -326,9 +367,10 @@ def syst_frequency_extraction(self): # calculate uncertainty of energy correction for pitch angle var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 - max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, - self.T_endpoint, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) + max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + #max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + # self.T_endpoint, + # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) # 0.16 is the trap quadratic term. 3.8317 is the first 0 in J'0 var_f0_reconstruction *= (8 * 0.16 * (3.8317*self.Experiment.L_over_D / (np.pi * beta(self.T_endpoint)))**2*max_ax_freq/endpoint_frequency)**2*(1/3.0) sigma_f0_reconstruction = np.sqrt(var_f0_reconstruction) diff --git a/test_analysis/CCA_Sensitivity.py b/test_analysis/CCA_Sensitivity.py index 9fe89eb9..670c2470 100644 --- a/test_analysis/CCA_Sensitivity.py +++ b/test_analysis/CCA_Sensitivity.py @@ -54,6 +54,39 @@ sens_curve.Run() +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", #Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "plot_path": "./sensitivity_vs_exposure_curve_CCA.pdf", + # optional + "figsize": (10,6), + "fontsize": 15, + "legend_location": "upper right", + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "density_axis": False, + "exposure_axis": True, + "cavity": True, + "add_PhaseII": False, + #"add_1year_1cav_point_to_last_ref": True, + "y_limits": [10e-3, 500], + "density_range": [1e12, 1e19], + "exposure_range": [1e-11, 1e10], + "main_curve_upper_label": r"CCA", #"Molecular, conservative", + #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", + "comparison_curve": False, + "upper_label_y_position": 0.7, + "label_x_position": 0.015, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.2e-10, #2e12 #0.0002 + "goals_y_rel_position": 0.4 + } +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() + + + diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0cfad6f5..155763c4 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -63,8 +63,8 @@ # Configuration for Sensitivity vs. frequency plot sens_config_dict2 = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_frequency.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", + "plot_path": "./sensitivity_vs_frequency_Oct-22-2024.pdf", # optional "figsize": (9,6), "fontsize": 15, @@ -149,43 +149,40 @@ # Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_density_curve_87deg_min_pitch.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Oct-30-f_flat-20.pdf", # optional "figsize": (7.0,6), "fontsize": 15, "track_length_axis": False, + "legend_location": "upper left", "molecular_axis": False, "atomic_axis": True, "density_axis": True, + "track_length_axis": True, "cavity": True, "y_limits": [2e-2, 4], - "density_range": [1e12,3e18], - #"efficiency_range": [0.0001, 1], - #"density_range": [1e8, 1e12], - "main_curve_upper_label": r'Phase IV scenario',#"Atomic, conservative", #r"Molecular"+"\n"+"Reaching target", - "comparison_curve_label": [#"Molecular, reaching target", - "Atomic, alternative scenario 1"], - #"Atomic, reaching target"], - # #["Molecular"+"\n"+"Conservative", "Atomic"+"\n"+"Conservative", r"Atomic"+"\n"+"Reaching target"], - "goals": {"Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, - "comparison_curve": False, - "comparison_curve_colors": ["red"], - "comparison_config_file_path": [#"/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", - "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #, - #"/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], + "density_range": [1e13,1e19], + "main_curve_upper_label": r"Phase III scenario: 1 GHz", + "goals": {"Phase III (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "comparison_curve": True, + "main_curve_color": "blue", + "comparison_curve_colors": ["red", "black"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", + "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 1.2e12, #0.0002 + "goals_x_position": 3e13, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { @@ -207,25 +204,25 @@ "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "add_1year_1cav_point_to_last_ref": False, - "y_limits": [0.1,20], + "y_limits": [1.5e-2, 1], #"density_range": [1e12,1e19], - "year_range": [1e-11, 1e4], + "year_range": [0.1,10**3], #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", - "main_curve_upper_label": r"Phase III scenario: 1 GHz", + "main_curve_upper_label": r"Phase III scenario: 1 GHz, V = 0.25 m$^3$", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], - #"comparison_curve_colors": ["blue", "darkred", "red"], + "comparison_curve_label": [r"Phase III scenario: 560 MHz, V = 1.2 m$^3$", r"Phase IV scenario: 150 MHz, V = 74 m$^3 \times 10$"], + "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, - "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.2e-10, #2e12 #0.0002 - "goals_y_rel_position": 0.4, + "label_x_position": 0.115, #1.5e15, #0.02, #1e14, + "goals_x_position": 0.115, #2e12 #0.0002 + "goals_y_rel_position": 0.65, } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) @@ -236,7 +233,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_exposure_curve_Oct-20-2024.pdf", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_exposure_curve_Oct-22-2024.pdf", "exposure_axis": True, # optional "figsize": (10,6), @@ -262,7 +259,7 @@ "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], - #"comparison_curve_colors": ["blue", "darkred", "red"], + "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, "lower_label_y_position": 0.17, @@ -412,39 +409,6 @@ #sens_curve.Run() -sens_config_dict = { - # required - "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", #Config_PhaseIII_325MHz_Experiment_conservative.cfg", - "plot_path": "./sensitivity_vs_exposure_curve_CCA.pdf", - # optional - "figsize": (10,6), - "fontsize": 15, - "legend_location": "upper right", - "track_length_axis": False, - "molecular_axis": False, - "atomic_axis": False, - "density_axis": False, - "exposure_axis": True, - "cavity": True, - "add_PhaseII": False, - #"add_1year_1cav_point_to_last_ref": True, - "y_limits": [10e-3, 500], - "density_range": [1e12, 1e19], - "exposure_range": [1e-11, 1e10], - "main_curve_upper_label": r"CCA", #"Molecular, conservative", - #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "comparison_curve": False, - "upper_label_y_position": 0.7, - "label_x_position": 0.015, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.2e-10, #2e12 #0.0002 - "goals_y_rel_position": 0.4 - } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() - - - From b141f9aca02b879e81078a9780dee0958171de38 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 30 Oct 2024 21:32:56 -0400 Subject: [PATCH 175/262] Include linkage between cavity power and flat-harmonic model trajectories --- mermithid/cavity/HannekeFunctions.py | 40 +++++++++++++++++++ .../sensitivity/SensitivityCavityFormulas.py | 11 +++-- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/mermithid/cavity/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py index a2f9f056..9a9556bb 100644 --- a/mermithid/cavity/HannekeFunctions.py +++ b/mermithid/cavity/HannekeFunctions.py @@ -72,6 +72,11 @@ def larmor_orbit_averaged_hanneke_power(r_position, z_position, loaded_Q, l_cav, random_angles = (np.linspace(0,1,n_points)+np.random.rand())*2*np.pi # equally spaced ponts on a circle with random offset x_random = electron_orbit_r*np.cos(random_angles) y_random = electron_orbit_r*np.sin(random_angles) + + r_scalar = False + if not hasattr(r_position, "__len__"): + r_scalar = True + r_position = np.array([r_position]) if hasattr(z_position, "__len__"): hanneke_powers = np.empty((len(r_position),len(z_position))) else: @@ -88,8 +93,43 @@ def larmor_orbit_averaged_hanneke_power(r_position, z_position, loaded_Q, l_cav, else: hanneke_power = hanneke_radiated_power(r_pos_orbit, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, tranverse_kinetic_energy, mode_frequency=mode_frequency) hanneke_powers[i] = np.mean(hanneke_power.T, axis=-1) + if r_scalar: + hanneke_powers = hanneke_powers[0] + return hanneke_powers +""" +def larmor_orbit_averaged_hanneke_power(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, + kinetic_energy=endpoint, pitch=np.pi/2, mode_frequency=None, n_points=100): + if mode_frequency is None: + # Assume that the center of the mode and the cyclotron frequency are identical + mode_frequency = cyclotron_frequency + tranverse_kinetic_energy = kinetic_energy*np.sin(pitch)**2 + + magnetic_field = magneticfield_from_frequency(cyclotron_frequency, kinetic_energy) + electron_orbit_r = larmor_radius(magnetic_field, kin_energy=kinetic_energy, pitch=pitch) + + random_angles = (np.linspace(0,1,n_points)+np.random.rand())*2*np.pi # equally spaced ponts on a circle with random offset + x_random = electron_orbit_r*np.cos(random_angles) + y_random = electron_orbit_r*np.sin(random_angles) + if hasattr(z_position, "__len__"): + hanneke_powers = np.empty((len(r_position),len(z_position))) + else: + hanneke_powers = np.empty(len(r_position)) + for i,r_pos_center in enumerate(r_position): + r_pos_orbit = np.sqrt((x_random+r_pos_center)**2 + y_random**2) + # Check for points outside the cavity + if np.any(np.abs(r_pos_orbit) > r_cav): + if hasattr(z_position, "__len__"): + hanneke_powers[i] = np.zeros(len(z_position)) + else: + hanneke_powers[i] = 0 + else: + hanneke_power = hanneke_radiated_power(r_pos_orbit, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, tranverse_kinetic_energy, mode_frequency=mode_frequency) + hanneke_powers[i] = np.mean(hanneke_power.T, axis=-1) + return hanneke_powers +""" + # Calculate the average radiated power for an electron with radius r_position in a box trap: def larmor_orbit_averaged_hanneke_power_box(r_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, kinetic_energy=endpoint, pitch=np.pi/2, mode_frequency=None, n_points=100): diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index dc060b34..52d5c20e 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -199,10 +199,15 @@ def PitchDependentTrappingEfficiency(self): def CavityPower(self): # from Hamish's atomic calculator #Jprime_0 = 3.8317 - + max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.Experiment.L_over_D*self.CavityRadius()*2, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) + #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power_box(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), - self.CavityLoadedQ(), + self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), + z_t, self.CavityLoadedQ(), 2*self.Experiment.L_over_D*self.cavity_radius, self.cavity_radius, frequency(self.T_endpoint, self.MagneticField.nominal_field))) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 155763c4..b307c906 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Oct-30-f_flat-20.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Oct-30-f_flat-50.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From 5eeae193d3b5906f92a9ff2caf1779e7f3034a39 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 3 Nov 2024 15:28:12 -0500 Subject: [PATCH 176/262] Divide TE011 mode power by 2, per Rick's findings --- mermithid/cavity/HannekeFunctions.py | 6 +++--- mermithid/misc/FakeTritiumDataFunctions.py | 5 ++++- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mermithid/cavity/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py index 9a9556bb..c58cb97b 100644 --- a/mermithid/cavity/HannekeFunctions.py +++ b/mermithid/cavity/HannekeFunctions.py @@ -24,7 +24,7 @@ def larmor_radius(magnetic_field, kin_energy=endpoint, pitch=np.pi/2): return gamma(kin_energy)*me*transverse_speed/(e*magnetic_field) # Hanneke factor for TE011 mode, for an electron at fixed location (r_position, z_position). -def hanneke_factor(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency): +def hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency): global bessel_derivative_zero # Calculate the lambda_mnp_squared factor mode_p = 1 # TE011 mode @@ -35,7 +35,7 @@ def hanneke_factor(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_fre constant_factor = - 2 * lambda_bessel_part * classical_electron_radius_c_squared #constant_factor_unitless = constant_factor/m**3/Hz**2 #print("Hanneke prefactor [units /m**3/Hz**2]", constant_factor_unitless) - lambda_mnp_squared = constant_factor / (z_L * r_cav**2) # This should be in units of Hz^2 + lambda_mnp_squared = constant_factor / (z_L * r_cav**2) / 2. # This should be in units of Hz^2 angular_part = special.jvp(0, bessel_derivative_zero * r_position/r_cav)**2 axial_part = np.sin(mode_p * np.pi/2 * (z_position/z_L + 1))**2 @@ -54,7 +54,7 @@ def hanneke_radiated_power(r_position, z_position, loaded_Q, l_cav, r_cav, cyclo # Assume that the center of the mode and the cyclotron frequency are identical mode_frequency = cyclotron_frequency - lambda_mnp_squared, delta = hanneke_factor(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency=mode_frequency) + lambda_mnp_squared, delta = hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency=mode_frequency) return tranverse_kinetic_energy*(2*loaded_Q/(1+delta**2))*lambda_mnp_squared/(mode_frequency*np.pi*2) # Calculate the radiated power for an electron at fixed location (r_position, z_position) with energy tranverse_kinetic_energy. diff --git a/mermithid/misc/FakeTritiumDataFunctions.py b/mermithid/misc/FakeTritiumDataFunctions.py index 5e36fe6e..4bd26fe2 100644 --- a/mermithid/misc/FakeTritiumDataFunctions.py +++ b/mermithid/misc/FakeTritiumDataFunctions.py @@ -140,7 +140,10 @@ def ephasespace(K, Q): Tritium beta spectrum definition """ -#Beta spectrum with a lower energy bound Kmin +#spectral_rate_in_window: Beta spectrum with a lower energy bound Kmin +#This function only is exact for the atomic case, or for a molecular case in the last +# ~18 eV of the spectrum (where molecular final states can be included later as a small +# gaussian broadening). def spectral_rate_in_window(K, Q, mnu, Kmin): if Q-mnu > K > Kmin: return GF**2.*Vud**2*Mnuc2/(2.*np.pi**3)*ephasespace(K, Q)*(Q - K)*np.sqrt((Q - K)**2 - (mnu)**2) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 52d5c20e..a16e842e 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -3,7 +3,7 @@ Author: R. Reimann, C. Claessens, T. Weiss, W. Van De Pontseele Date:06/07/2023 -The statistical method and formulars are described in +The statistical method and formulas are described in CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np From e751050fa84f540faea31c4da7d51fdce79a20b5 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 3 Nov 2024 19:51:17 -0500 Subject: [PATCH 177/262] Fix typo --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index a16e842e..593439c0 100644 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -453,7 +453,7 @@ def print_SNRs(self, rho=None): logger.info("SNR for 1 ms: {}".format(SNR_1ms)) - logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) + logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(self.sigma_f_CRLB_slope_fitted/Hz)) logger.info("CRLB constant: {}".format(self.CRLB_constant)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index b307c906..c9a5a6a1 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Oct-30-f_flat-50.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Nov-3-f_flat-80_RickFactorOfHalf.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From 59cb5f4aa8ac0b4e294e9b9f24b0b98168c24ff1 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 3 Nov 2024 22:56:51 -0500 Subject: [PATCH 178/262] Print out f_c uncertainty from CRLB in Hz --- .../sensitivity/SensitivityCavityFormulas.py | 1 + mermithid/sensitivity/SensitivityFormulas.py | 1 + test_analysis/Cavity_Sensitivity_analysis.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 593439c0..63e53d85 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -345,6 +345,7 @@ def syst_frequency_extraction(self): # non constant slope self.sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor if self.CRLB_constant > 10: sigma_f_CRLB = self.sigma_f_CRLB_slope_fitted + self.sigma_f_c_CRLB = sigma_f_CRLB """ CRLB_constant = 6 sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 30eef124..94256300 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -243,6 +243,7 @@ def print_systematics(self): pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.SystSens())/meV), "meV") + logger.info("f_c uncertainty: {} Hz".format(self.sigma_f_c_CRLB/Hz)) return np.sqrt(1.64*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index c9a5a6a1..76713147 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -180,9 +180,9 @@ "goals_x_position": 3e13, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { @@ -371,8 +371,8 @@ # Configuration for CCA Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_CCA_Experiment.cfg", - "plot_path": "./cca_sensitivity_vs_density_curve.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_CCA_pitchAngUncertVerification.cfg", + "plot_path": "./cca_sensitivity_vs_density_curve_new_config.pdf", # optional "figsize": (7.0,6), "track_length_axis": True, @@ -404,9 +404,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() From d4fb413040a9136a0059586d50bc7ea7ad4bca86 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 4 Nov 2024 21:06:13 -0500 Subject: [PATCH 179/262] Added comments pointing out Rick's factor of 1/2 in cavity power and including sources --- mermithid/cavity/HannekeFunctions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mermithid/cavity/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py index c58cb97b..60e92826 100644 --- a/mermithid/cavity/HannekeFunctions.py +++ b/mermithid/cavity/HannekeFunctions.py @@ -24,6 +24,11 @@ def larmor_radius(magnetic_field, kin_energy=endpoint, pitch=np.pi/2): return gamma(kin_energy)*me*transverse_speed/(e*magnetic_field) # Hanneke factor for TE011 mode, for an electron at fixed location (r_position, z_position). +# Note: This does not exactly match Hanneke, because it includes Rick Mueller's finding that +# the power in the TE011 mode is half of that in Hanneke's work. (Pozar also missed this +# factor!) For more on Rick's work, see: +# https://3.basecamp.com/3700981/buckets/3107037/documents/7796088022 +# https://3.basecamp.com/3700981/buckets/3107037/uploads/7834409442 def hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency): global bessel_derivative_zero # Calculate the lambda_mnp_squared factor @@ -35,6 +40,8 @@ def hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotr constant_factor = - 2 * lambda_bessel_part * classical_electron_radius_c_squared #constant_factor_unitless = constant_factor/m**3/Hz**2 #print("Hanneke prefactor [units /m**3/Hz**2]", constant_factor_unitless) + + # "Rick's factor of 1/2" is in the line below! lambda_mnp_squared = constant_factor / (z_L * r_cav**2) / 2. # This should be in units of Hz^2 angular_part = special.jvp(0, bessel_derivative_zero * r_position/r_cav)**2 axial_part = np.sin(mode_p * np.pi/2 * (z_position/z_L + 1))**2 From c7b024f1621ae8495dc26c69e7eb61157844c03c Mon Sep 17 00:00:00 2001 From: root Date: Tue, 5 Nov 2024 12:50:50 -0500 Subject: [PATCH 180/262] added independent trap L/D dependency for electron axial motion --- .../sensitivity/SensitivityCavityFormulas.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 63e53d85..a038c362 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -27,7 +27,7 @@ def db_to_pwr_ratio(q_db): return 10**(q_db/10) -def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, kin_energy, flat_fraction=0.5, trajectory = None): +def axial_motion(magnetic_field, pitch, trap_length, minimum_trapped_pitch, kin_energy, flat_fraction=0.5, trajectory = None): # returns the axial motion frequency and a trajectory of point along the axial motion # also return the average magnetic field seen by the electron # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable @@ -36,7 +36,7 @@ def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, ki minimum_trapped_pitch/180*np.pi # Axial motion: - z_w = cavity_length/2 + z_w = trap_length/2 speed = beta(kin_energy)*c0 transverse_speed = speed*np.cos(pitch) tan_min = np.tan(minimum_trapped_pitch) @@ -201,7 +201,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) @@ -227,7 +227,7 @@ def CavityLoadedQ(self): # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) required_bw_axialfrequency = max_ax_freq @@ -373,7 +373,12 @@ def syst_frequency_extraction(self): # calculate uncertainty of energy correction for pitch angle var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 - max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.T_endpoint, + flat_fraction=self.MagneticField.trap_flat_fraction) #max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) From 98a42f294ccf079603c06faf0c7a80613ec74426 Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 5 Nov 2024 13:07:11 -0500 Subject: [PATCH 181/262] added independent trap L/D dependency for electron axial motion --- .../sensitivity/SensitivityCavityFormulas.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 63e53d85..a038c362 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -27,7 +27,7 @@ def db_to_pwr_ratio(q_db): return 10**(q_db/10) -def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, kin_energy, flat_fraction=0.5, trajectory = None): +def axial_motion(magnetic_field, pitch, trap_length, minimum_trapped_pitch, kin_energy, flat_fraction=0.5, trajectory = None): # returns the axial motion frequency and a trajectory of point along the axial motion # also return the average magnetic field seen by the electron # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable @@ -36,7 +36,7 @@ def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, ki minimum_trapped_pitch/180*np.pi # Axial motion: - z_w = cavity_length/2 + z_w = trap_length/2 speed = beta(kin_energy)*c0 transverse_speed = speed*np.cos(pitch) tan_min = np.tan(minimum_trapped_pitch) @@ -201,7 +201,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) @@ -227,7 +227,7 @@ def CavityLoadedQ(self): # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) required_bw_axialfrequency = max_ax_freq @@ -373,7 +373,12 @@ def syst_frequency_extraction(self): # calculate uncertainty of energy correction for pitch angle var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 - max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, + self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.T_endpoint, + flat_fraction=self.MagneticField.trap_flat_fraction) #max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) From 4274186c1dc1fb0be0f9f4c5b391ac4cc6beea9a Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Wed, 6 Nov 2024 15:31:34 -0500 Subject: [PATCH 182/262] added independent trap L/D dependency for magnetic field flat harmonic calculation --- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index a038c362..1e2787df 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -60,8 +60,8 @@ def axial_motion(magnetic_field, pitch, trap_length, minimum_trapped_pitch, kin_ return axial_frequency, magnetic_field_avg, z_t -def magnetic_field_flat_harmonic(z,magnetic_field, cavity_length,minimum_trapped_pitch, flat_fraction=0.5): - z_w = cavity_length/2 +def magnetic_field_flat_harmonic(z, magnetic_field, trap_length, minimum_trapped_pitch, flat_fraction=0.5): + z_w = trap_length/2 a = z_w*(1-flat_fraction)*np.tan(minimum_trapped_pitch) return magnetic_field*(1+np.heaviside(np.abs(z)-z_w*flat_fraction, 0.5)*(np.abs(z)-z_w*flat_fraction)**2/a**2) From bddbc54d21fad1daf999926b2938d74716e7427d Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 8 Nov 2024 09:41:30 -0500 Subject: [PATCH 183/262] Corrected error caught by Ben F with not converting deg to rad --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- mermithid/sensitivity/SensitivityFormulas.py | 2 +- test_analysis/Cavity_Sensitivity_analysis.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 63e53d85..0d599d53 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -33,7 +33,7 @@ def axial_motion(magnetic_field, pitch, cavity_length, minimum_trapped_pitch, ki # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable # See LUCKEY write-up for a little more on Talia's "flat fraction" trap model pitch = pitch/180*np.pi - minimum_trapped_pitch/180*np.pi + minimum_trapped_pitch = minimum_trapped_pitch/180*np.pi # Axial motion: z_w = cavity_length/2 diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 94256300..669894e4 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -3,7 +3,7 @@ Author: R. Reimann, C. Claessens Date:11/17/2020 -The statistical method and formulars are described in +The statistical method and formulas are described in CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 76713147..32e15b10 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Nov-3-f_flat-80_RickFactorOfHalf.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Nov-8-f_flat-20_fix-radian-deg.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -180,9 +180,9 @@ "goals_x_position": 3e13, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { @@ -404,9 +404,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() From 03193dd462263b9270e6a729ca3536f97778a820 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 11 Nov 2024 18:08:47 -0500 Subject: [PATCH 184/262] Implemented cosmic ray background rate calculation --- .../Sensitivity/CavitySensitivityCurveProcessor.py | 2 ++ mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- mermithid/sensitivity/SensitivityFormulas.py | 5 ++++- test_analysis/Cavity_Sensitivity_analysis.py | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index a37d9a8b..35a95a29 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -320,6 +320,7 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) + logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) @@ -354,6 +355,7 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) + logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 0d599d53..ec55f8aa 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -134,7 +134,7 @@ class CavitySensitivity(Sensitivity): """ Documentation: * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 - * Talias sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 + * Talia's sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 669894e4..ef796603 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -101,8 +101,11 @@ def SignalRate(self): def BackgroundRate(self): """background rate, can be calculated from multiple components. + Current, RF noise and cosmic ray backgrounds are included. Assumes that background rate is constant over considered energy / frequency range.""" - return self.Experiment.background_rate_per_eV + self.cosmic_ray_background = self.Experiment.cosmic_ray_bkgd_per_tritium_particle*self.Experiment.number_density*self.effective_volume + self.background_rate = self.Experiment.RF_background_rate_per_eV + self.cosmic_ray_background + return self.background_rate def SignalEvents(self): """Number of signal events.""" diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 32e15b10..0bb950ad 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -1,6 +1,6 @@ """ Script to make Sensitivty plots for cavity experiments -Author: C. Claessens, T. Weiss +Author: C. Claessens, T. E. Weiss Date: October 6, 2023 """ @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_Nov-8-f_flat-20_fix-radian-deg.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_with-cosmic-ray-bkgd_Nov-11-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From 6b1d9e7c199974da7ee0c8a97c78e9b51a52478b Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Mon, 18 Nov 2024 21:11:39 -0500 Subject: [PATCH 185/262] added different L/D support for traps --- .../sensitivity/SensitivityCavityFormulas.py | 36 +++++++++++++------ tests/Sensitivity_test.py | 12 +++---- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index ed054f0f..b9367af1 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -142,6 +142,8 @@ def __init__(self, config_path): Sensitivity.__init__(self, config_path) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + if self.Experiment.trap_L_over_D == 0: + self.Experiment.trap_L_over_D = self.Experiment.L_over_D self.CRLB_constant = 12 #self.CRLB_constant = 90 @@ -163,19 +165,33 @@ def CavityRadius(self): def CavityVolume(self): #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) - self.total_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities + self.total_cavity_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) - logger.info("Radius: {} cm".format(round(self.cavity_radius/cm, 3))) - logger.info("Length: {} cm".format(round(2*self.cavity_radius*self.Experiment.L_over_D/cm, 3))) - logger.info("Total volume {} m^3".format(round(self.total_volume/m**3))) + logger.info("Cavity radius: {} cm".format(round(self.cavity_radius/cm, 3))) + logger.info("Cavity length: {} cm".format(round(2*self.cavity_radius*self.Experiment.L_over_D/cm, 3))) + logger.info("Total cavity volume {} m^3".format(round(self.total_cavity_volume/m**3)))\ - return self.total_volume + return self.total_cavity_volume + + + # ELECTRON TRAP + def TrapVolume(self): + # Total volume of the electron traps in all cavities + self.total_trap_volume = 2*self.cavity_radius*self.Experiment.trap_L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities + + logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3))) + logger.info("Trap length: {} cm".format(round(2*self.cavity_radius*self.Experiment.trap_L_over_D/cm, 3))) + logger.info("Total trap volume {} m^3 ()".format(round(self.total_trap_volume/m**3))) + + return self.total_trap_volume + + def EffectiveVolume(self): if self.Efficiency.usefixedvalue: - self.effective_volume = self.total_volume * self.Efficiency.fixed_efficiency + self.effective_volume = self.total_trap_volume * self.Efficiency.fixed_efficiency else: # radial and detection efficiency are configured in the config file #logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) @@ -183,7 +199,7 @@ def EffectiveVolume(self): #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - self.effective_volume = self.total_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() + self.effective_volume = self.total_trap_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor @@ -201,7 +217,7 @@ def CavityPower(self): #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) @@ -227,7 +243,7 @@ def CavityLoadedQ(self): # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) required_bw_axialfrequency = max_ax_freq @@ -375,7 +391,7 @@ def syst_frequency_extraction(self): var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2 if self.Experiment.trap_L_over_D else self.Experiment.L_over_D*self.CavityRadius()*2, + self.Experiment.trap_L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) diff --git a/tests/Sensitivity_test.py b/tests/Sensitivity_test.py index e75500e5..d31cd348 100644 --- a/tests/Sensitivity_test.py +++ b/tests/Sensitivity_test.py @@ -22,7 +22,7 @@ def test_SensitivityCurveProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "plot_path": "./sensitivity_vs_exposure_curve.pdf", # optional "figsize": (10,6), @@ -43,10 +43,10 @@ def test_SensitivityCurveProcessor(self): "main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", "goals": {"Phase III (0.2 eV)": (0.2**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], - "comparison_curve_label": [r"Molecular, reaching PIII target", "Atomic, conservative", "Atomic, reaching PIV target"], + "comparison_curve_label": [r"150Hz, PIII 87 degrees", "Atomic, conservative", "Atomic, reaching PIV target"], "comparison_curve_colors": ["blue", "darkred", "red"], #"B_inhomogeneity": np.linspace(0.1, 2.1, 10)*1e-6, #"B_inhom_uncertainty": 0.01, @@ -84,7 +84,7 @@ def test_SensitivityCurveProcessor(self): "goals": {"Phase III (0.2 eV)": 0.2, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, #"comparison_curve_colors": ["red"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment.cfg"], "goals_x_position": 1.2e12, #0.0002 @@ -104,7 +104,7 @@ def test_SensitivityProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg" + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg" } # sens = AnalyticSensitivityEstimation("sensitivity_processor") # sens.Configure(sens_config_dict) @@ -124,7 +124,7 @@ def test_ConstantSensitivityCurvesProcessor(self): sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_PhaseIII_1GHz_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "sensitivity_target": [0.4**2/1.64]#, 0.7**2/1.64, 1**2/1.64] } #sens = ConstantSensitivityParameterPlots("sensitivity_processor") From 118266114d6e0ad984a7f21e8f773d44dceb2f7b Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Thu, 21 Nov 2024 23:06:15 -0500 Subject: [PATCH 186/262] Implemented trap length instead of trap L/D, renamed cavity L/D --- .../sensitivity/SensitivityCavityFormulas.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index b9367af1..86ffb066 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -142,8 +142,6 @@ def __init__(self, config_path): Sensitivity.__init__(self, config_path) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) - if self.Experiment.trap_L_over_D == 0: - self.Experiment.trap_L_over_D = self.Experiment.L_over_D self.CRLB_constant = 12 #self.CRLB_constant = 90 @@ -157,20 +155,26 @@ def __init__(self, config_path): self.PitchDependentTrappingEfficiency() self.CavityPower() + #Get trap length from cavity length if not specified + if self.Experiment.trap_length == 0: + self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + # else: + # self.Experiment.trap_length = self.Experiment.trap_length/cm + # CAVITY def CavityRadius(self): axial_mode_index = 1 - self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.L_over_D**2)) + self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.cavity_L_over_D**2)) return self.cavity_radius def CavityVolume(self): #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) - self.total_cavity_volume = 2*self.cavity_radius*self.Experiment.L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities + self.total_cavity_volume = 2*self.cavity_radius*self.Experiment.cavity_L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Cavity radius: {} cm".format(round(self.cavity_radius/cm, 3))) - logger.info("Cavity length: {} cm".format(round(2*self.cavity_radius*self.Experiment.L_over_D/cm, 3))) + logger.info("Cavity length: {} cm".format(round(2*self.cavity_radius*self.Experiment.cavity_L_over_D/cm, 3))) logger.info("Total cavity volume {} m^3".format(round(self.total_cavity_volume/m**3)))\ return self.total_cavity_volume @@ -179,10 +183,10 @@ def CavityVolume(self): # ELECTRON TRAP def TrapVolume(self): # Total volume of the electron traps in all cavities - self.total_trap_volume = 2*self.cavity_radius*self.Experiment.trap_L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities + self.total_trap_volume = self.Experiment.trap_length*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3))) - logger.info("Trap length: {} cm".format(round(2*self.cavity_radius*self.Experiment.trap_L_over_D/cm, 3))) + logger.info("Trap length: {} cm".format(round(self.Experiment.trap_length/cm, 3))) logger.info("Total trap volume {} m^3 ()".format(round(self.total_trap_volume/m**3))) return self.total_trap_volume @@ -217,14 +221,14 @@ def CavityPower(self): #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2, + self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) - #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W + #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.cavity_L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), z_t, self.CavityLoadedQ(), - 2*self.Experiment.L_over_D*self.cavity_radius, + 2*self.Experiment.cavity_L_over_D*self.cavity_radius, self.cavity_radius, frequency(self.T_endpoint, self.MagneticField.nominal_field))) return self.signal_power @@ -238,12 +242,12 @@ def CavityLoadedQ(self): #self.loaded_q =1/(0.22800*((90-self.FrequencyExtraction.minimum_angle_in_bandwidth)*np.pi/180)**2+2**2*0.01076**2/(4*0.22800)) endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) - #required_bw_axialfrequency = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + #required_bw_axialfrequency = axial_frequency(self.Experiment.cavity_L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2, + self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) required_bw_axialfrequency = max_ax_freq @@ -391,15 +395,15 @@ def syst_frequency_extraction(self): var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.Experiment.trap_L_over_D*self.CavityRadius()*2, + self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) - #max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, + #max_ax_freq = axial_frequency(self.Experiment.cavity_L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) # 0.16 is the trap quadratic term. 3.8317 is the first 0 in J'0 - var_f0_reconstruction *= (8 * 0.16 * (3.8317*self.Experiment.L_over_D / (np.pi * beta(self.T_endpoint)))**2*max_ax_freq/endpoint_frequency)**2*(1/3.0) + var_f0_reconstruction *= (8 * 0.16 * (3.8317*self.Experiment.cavity_L_over_D / (np.pi * beta(self.T_endpoint)))**2*max_ax_freq/endpoint_frequency)**2*(1/3.0) sigma_f0_reconstruction = np.sqrt(var_f0_reconstruction) self.sigma_K_reconstruction = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f0_reconstruction*c0**2 From 42bcf2fa551680f533cd6c6ca16dc23b80b491bf Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Thu, 21 Nov 2024 23:20:49 -0500 Subject: [PATCH 187/262] removed unnecessary lines --- mermithid/sensitivity/SensitivityCavityFormulas.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 86ffb066..109ec6cb 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -158,8 +158,7 @@ def __init__(self, config_path): #Get trap length from cavity length if not specified if self.Experiment.trap_length == 0: self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D - # else: - # self.Experiment.trap_length = self.Experiment.trap_length/cm + # CAVITY def CavityRadius(self): From 76f956a7dabb9156365c3b3f5d4035a63f3dabba Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 25 Nov 2024 12:30:17 -0500 Subject: [PATCH 188/262] Using the desired pitch angle in the config file for f_a uncertainty calculation, instead of the min pitch --- .../sensitivity/SensitivityCavityFormulas.py | 2 +- mermithid/sensitivity/SensitivityFormulas.py | 2 +- test_analysis/Cavity_Sensitivity_analysis.py | 25 +++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index ec55f8aa..3218075c 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -373,7 +373,7 @@ def syst_frequency_extraction(self): # calculate uncertainty of energy correction for pitch angle var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 - max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.pitch_angle/deg, self.Experiment.L_over_D*self.CavityRadius()*2, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) #max_ax_freq = axial_frequency(self.Experiment.L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index ef796603..d65159f8 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -101,7 +101,7 @@ def SignalRate(self): def BackgroundRate(self): """background rate, can be calculated from multiple components. - Current, RF noise and cosmic ray backgrounds are included. + Currently, RF noise and cosmic ray backgrounds are included. Assumes that background rate is constant over considered energy / frequency range.""" self.cosmic_ray_background = self.Experiment.cosmic_ray_bkgd_per_tritium_particle*self.Experiment.number_density*self.effective_volume self.background_rate = self.Experiment.RF_background_rate_per_eV + self.cosmic_ray_background diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0bb950ad..492bf883 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,8 +149,8 @@ # Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_with-cosmic-ray-bkgd_Nov-11-2024.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_fa-uncertainty-comparison_Nov-15-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -163,14 +163,13 @@ "cavity": True, "y_limits": [2e-2, 4], "density_range": [1e13,1e19], - "main_curve_upper_label": r"Phase III scenario: 1 GHz", + "main_curve_upper_label": r"Phase III scenario: 560 MHz", #Phase III scenario: 1 GHz", "goals": {"Phase III (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, "main_curve_color": "blue", - "comparison_curve_colors": ["red", "black"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", - "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase III scenario: 560 MHz", r"Phase IV scenario: 150 MHz"], + "comparison_curve_colors": ["red"], # "black"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase IV scenario: 150 MHz"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, @@ -180,9 +179,9 @@ "goals_x_position": 3e13, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { @@ -404,9 +403,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() From d18c0d2caac1f63c7eb6191ba0e457577247e298 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 25 Nov 2024 12:58:44 -0500 Subject: [PATCH 189/262] Run Razu's changes to include diff trap and cavity lengths; debug and make changes so that it runs --- mermithid/sensitivity/SensitivityCavityFormulas.py | 5 +++-- test_analysis/Cavity_Sensitivity_analysis.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 109ec6cb..8f6f0ecd 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -186,13 +186,14 @@ def TrapVolume(self): logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3))) logger.info("Trap length: {} cm".format(round(self.Experiment.trap_length/cm, 3))) - logger.info("Total trap volume {} m^3 ()".format(round(self.total_trap_volume/m**3))) + logger.info("Total trap volume {} m^3".format(round(self.total_trap_volume/m**3))) return self.total_trap_volume def EffectiveVolume(self): + self.total_trap_volume = self.TrapVolume() if self.Efficiency.usefixedvalue: self.effective_volume = self.total_trap_volume * self.Efficiency.fixed_efficiency else: @@ -496,4 +497,4 @@ def print_Efficiencies(self): logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) - logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) + logger.info("Total efficiency: {}".format(self.effective_volume/self.total_trap_volume)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0bb950ad..20c0cc1a 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,9 +150,9 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_with-cosmic-ray-bkgd_Nov-11-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_diff_l_over_d_Nov-25-2024.pdf", # optional - "figsize": (7.0,6), + "figsize": (7.0,6), "fontsize": 15, "track_length_axis": False, "legend_location": "upper left", From 98e8e46d44ae8d795e4f75597574c200ed7ed7c6 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 25 Nov 2024 13:04:17 -0500 Subject: [PATCH 190/262] Use inputted pitch instead of min pitch for f_a uncertainty calculation --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 8f6f0ecd..1ec8f865 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -394,7 +394,7 @@ def syst_frequency_extraction(self): # calculate uncertainty of energy correction for pitch angle var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.FrequencyExtraction.pitch_angle/deg, self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, self.T_endpoint, From 3a4baf9eeb8e2e83d9e574236a42135b9783dcce Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 6 Dec 2024 13:02:24 -0500 Subject: [PATCH 191/262] Removed factor of 1/2 in cavity power; incorporated carrier_power_fraction variation variable in energy resolution calculation and printing of SNRs --- mermithid/cavity/HannekeFunctions.py | 9 +--- .../sensitivity/SensitivityCavityFormulas.py | 42 ++++++++++++------- test_analysis/Cavity_Sensitivity_analysis.py | 14 +++---- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/mermithid/cavity/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py index 60e92826..983c1f92 100644 --- a/mermithid/cavity/HannekeFunctions.py +++ b/mermithid/cavity/HannekeFunctions.py @@ -24,11 +24,7 @@ def larmor_radius(magnetic_field, kin_energy=endpoint, pitch=np.pi/2): return gamma(kin_energy)*me*transverse_speed/(e*magnetic_field) # Hanneke factor for TE011 mode, for an electron at fixed location (r_position, z_position). -# Note: This does not exactly match Hanneke, because it includes Rick Mueller's finding that -# the power in the TE011 mode is half of that in Hanneke's work. (Pozar also missed this -# factor!) For more on Rick's work, see: -# https://3.basecamp.com/3700981/buckets/3107037/documents/7796088022 -# https://3.basecamp.com/3700981/buckets/3107037/uploads/7834409442 +# Note: We don't "halve the power," given this from Rick: https://3.basecamp.com/3700981/buckets/3107037/uploads/8101664058. def hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotron_frequency, mode_frequency): global bessel_derivative_zero # Calculate the lambda_mnp_squared factor @@ -41,8 +37,7 @@ def hanneke_factor_TE011(r_position, z_position, loaded_Q, l_cav, r_cav, cyclotr #constant_factor_unitless = constant_factor/m**3/Hz**2 #print("Hanneke prefactor [units /m**3/Hz**2]", constant_factor_unitless) - # "Rick's factor of 1/2" is in the line below! - lambda_mnp_squared = constant_factor / (z_L * r_cav**2) / 2. # This should be in units of Hz^2 + lambda_mnp_squared = constant_factor / (z_L * r_cav**2) # This should be in units of Hz^2 angular_part = special.jvp(0, bessel_derivative_zero * r_position/r_cav)**2 axial_part = np.sin(mode_p * np.pi/2 * (z_position/z_L + 1))**2 diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 1ec8f865..0634be0f 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -267,8 +267,11 @@ def CavityLoadedQ(self): # SYSTEMATICS # Generic systematics are implemented in the parent class in SensitivityFormulas.py - def calculate_tau_snr(self, time_window, sideband_power_fraction=1): - + def calculate_tau_snr(self, time_window, power_fraction=1): + """ + power_fraction may be used as a carrier or a sideband power fraction, + relative to the power of a 90 degree carrier. + """ endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) # Cavity coupling @@ -298,7 +301,7 @@ def calculate_tau_snr(self, time_window, sideband_power_fraction=1): # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # logger.info("Power: {}".format(Pe/W)) - Pe = self.signal_power * sideband_power_fraction + Pe = self.signal_power * power_fraction P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) self.received_power = P_signal_received @@ -349,8 +352,8 @@ def syst_frequency_extraction(self): self.time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/self.slope - tau_snr_full_length = self.calculate_tau_snr(self.time_window) - tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero) + tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.carrier_power_fraction) + tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero, self.FrequencyExtraction.carrier_power_fraction) # use different crlb based on slope @@ -457,26 +460,33 @@ def print_SNRs(self, rho=None): logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") track_duration = self.time_window - tau_snr = self.calculate_tau_snr(track_duration, sideband_power_fraction=1) + tau_snr_90deg = self.calculate_tau_snr(track_duration, power_fraction=1) + #For an example carrier: + tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) - SNR_1eV = 1/eV_bandwidth/tau_snr - SNR_track_duration = track_duration/tau_snr - SNR_1ms = 0.001*s/tau_snr + SNR_1eV_90deg = 1/eV_bandwidth/tau_snr_90deg + SNR_track_duration_90deg = track_duration/tau_snr_90deg + SNR_1ms_90deg = 0.001*s/tau_snr_90deg + + SNR_1eV_ex_carrier = 1/eV_bandwidth/tau_snr_ex_carrier + SNR_track_duration_ex_carrier = track_duration/tau_snr_ex_carrier + SNR_1ms_ex_carrier = 0.001*s/tau_snr_ex_carrier logger.info("Number density: {} m^-3".format(self.Experiment.number_density*m**3)) logger.info("Track duration: {}ms".format(track_duration/ms)) - logger.info("tau_SNR: {}s".format(tau_snr/s)) + logger.info("tau_SNR for 90° carrier: {}s".format(tau_snr_90deg/s)) + logger.info("tau_SNR for carrier used in calculation (see config file): {}s".format(tau_snr_ex_carrier/s)) logger.info("Sampling duration for 1eV: {}ms".format(1/eV_bandwidth/ms)) - logger.info("Received power: {}W".format(self.received_power/W)) + logger.info("Received power for 90° carrier: {}W".format(self.received_power/W)) logger.info("Noise temperature: {}K".format(self.noise_temp/K)) logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) - logger.info("SNR for 1eV bandwidth: {}".format(SNR_1eV)) - logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) - logger.info("SNR for track duration: {}".format(SNR_track_duration)) - logger.info("SNR for 1 ms: {}".format(SNR_1ms)) + logger.info("SNRs of carriers (90°, used in calc) for 1eV bandwidth: {}, {}".format(SNR_1eV_90deg, SNR_1eV_ex_carrier)) + #logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) + logger.info("SNRs of carriers (90°, used in calc) for track duration: {}, {}".format(SNR_track_duration_90deg, SNR_track_duration_ex_carrier)) + logger.info("SNR of carriers (90°, used in calc) for 1 ms: {}, {}".format(SNR_1ms_90deg, SNR_1ms_ex_carrier)) logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) @@ -484,7 +494,7 @@ def print_SNRs(self, rho=None): logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(self.sigma_f_CRLB_slope_fitted/Hz)) logger.info("CRLB constant: {}".format(self.CRLB_constant)) - return self.noise_temp, SNR_1eV, track_duration + return self.noise_temp, SNR_1eV_90deg, track_duration def print_Efficiencies(self): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 7728ff11..393bd509 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_diff_l_over_d_Nov-25-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_factor_2_back_and_reduced_carrier_power_frac_Dec-6-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -179,9 +179,9 @@ "goals_x_position": 3e13, #0.0002 "plot_key_parameters": True } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { @@ -403,9 +403,9 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() From cfc885ce7ddd8cc91963764a04bf504db16b7a3a Mon Sep 17 00:00:00 2001 From: benanator77 Date: Fri, 6 Dec 2024 14:22:37 -0800 Subject: [PATCH 192/262] Minor adjustement to parameter scan csv output digits. --- .../processors/Sensitivity/SensitivityParameterScanProcessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 96119243..465eeec3 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -281,7 +281,7 @@ def InternalRun(self): results_array = [np.array(self.scan_parameter_values/self.scan_parameter_unit),np.array(self.noise_temp),np.array(self.SNR), np.array(self.optimum_rhos)*(m**3),np.array(self.track_duration),np.array(self.total_sigma), 1000*np.array(self.optimum_limits)/eV,np.array(self.sys_lim)] - fmt_array = '%.2f','%.3f','%.1f','%.2E','%.2f','%.1f','%.1f','%.1f' + fmt_array = '%.2g','%.3f','%.1f','%.2E','%.2f','%.1f','%.1f','%.1f' header_string = 'Param Value, Noise temperature /K, SNR, Gas Density /m3, Track Length /ms, Resolution, Sensitivity /meV, Systematic Limit /meV' np.savetxt("results_array_{}.csv".format(param),np.array(results_array).T,delimiter=',',fmt=fmt_array,header=header_string) logger.info("Scan parameter: {}".format(self.scan_parameter_name)) From 1fe8035e6d2bf6805fc26c3fe6a27a485ba1d8c8 Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 10 Dec 2024 00:36:09 -0500 Subject: [PATCH 193/262] added trapping efficiency calculation --- .../sensitivity/SensitivityCavityFormulas.py | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 1ec8f865..fb9982a7 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -129,6 +129,55 @@ def t_effective(t_physical, cyclotron_frequency): else: return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) +# Trapping efficiency from axial field variation. +def trapping_efficiency(z_range, radial_field_variation, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): + + """ + Calculate the trapping efficiency for a given trap length and flat fraction. + + The trapping efficiency is computed using the formula: + epsilon(z) = sqrt(1 - B(z)/B_max(z)) + where B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. + + Parameters + ---------- + z_range : float + The axial range (in z-direction, from trap center) over which electron trapping happens. + radial_field_variation : float + Predefined radial variation of magnetic fields, constant for all z's, temporary solution for efficiency calculation. + bg_magnetic_field : float + The background magnetic field. + min_pitch_angle : float + Minimum pitch angle to be trapped. + trap_flat_fraction : float, optional + Flat fraction of the trap. Default is 0.5. + plotting : bool, optional + If True, generates plots of the trapping efficiency. Default is False. + + Returns + ------- + mean_efficiency : float + The mean trapping efficiency across the specified trapping range. + + Notes + ----- + The magnetic field profile is computed using the `magnetic_field_flat_harmonic` function, currently it only produces z-profile of the trap without radial variation. + Radial variation is assumed to be constant for all radii, and set during function call. + The mean trapping efficiency is averaged over the region where the field is non-zero. + """ + + zs = np.linspace(-z_range, z_range, 100) + + profiles = [] + for z in zs: + profiles.append(magnetic_field_flat_harmonic(z, bg_magnetic_field, z_range*2, min_pitch_angle, trap_flat_fraction)) + + mean_efficiency = np.mean(np.array([np.mean(np.sqrt(radial_field_variation/b_at_z)) for b_at_z in profiles])) + + return mean_efficiency + + + ############################################################################### class CavitySensitivity(Sensitivity): """ @@ -147,7 +196,15 @@ def __init__(self, config_path): #self.CRLB_constant = 90 if hasattr(self.FrequencyExtraction, "crlb_constant"): self.CRLB_constant = self.FrequencyExtraction.crlb_constant - logger.info("Using configured CRLB constant") + logger.info("Using configured CRLB constant") + + #Calculate position dependent trapping efficiency + self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, + radial_field_variation = 0.01*mT if not self.MagneticField.radial_variation else self.MagneticField.radial_variation, + bg_magnetic_field = self.MagneticField.nominal_field, + min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, + trap_flat_fraction = self.MagneticField.trap_flat_fraction, + plotting= True) self.CavityRadius() self.CavityVolume() @@ -158,7 +215,7 @@ def __init__(self, config_path): #Get trap length from cavity length if not specified if self.Experiment.trap_length == 0: self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D - + # CAVITY def CavityRadius(self): @@ -203,7 +260,7 @@ def EffectiveVolume(self): #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - self.effective_volume = self.total_trap_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency() + self.effective_volume = self.total_trap_volume*self.Efficiency.radial_efficiency*self.Efficiency.detection_efficiency*self.PitchDependentTrappingEfficiency()*self.pos_dependent_trapping_efficiency #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor @@ -259,7 +316,7 @@ def CavityLoadedQ(self): # Cavity coupling self.loaded_q = endpoint_frequency/required_bw # FWHM return self.loaded_q - + # SENSITIVITY # see parent class in SensitivityFormulas.py @@ -493,6 +550,7 @@ def print_Efficiencies(self): # radial and detection efficiency are configured in the config file logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Trapping efficiency: {}".format(self.pos_dependent_trapping_efficiency)) logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) From 164e39a558c0cb1ded3aae83944816c090c4f466 Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 10 Dec 2024 14:54:31 -0500 Subject: [PATCH 194/262] replaced fixed variation w/ percent variation --- .../sensitivity/SensitivityCavityFormulas.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index fb9982a7..11f03b84 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -130,21 +130,21 @@ def t_effective(t_physical, cyclotron_frequency): return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) # Trapping efficiency from axial field variation. -def trapping_efficiency(z_range, radial_field_variation, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): +def trapping_efficiency(z_range, percent_radial_field_increase, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): """ Calculate the trapping efficiency for a given trap length and flat fraction. The trapping efficiency is computed using the formula: - epsilon(z) = sqrt(1 - B(z)/B_max(z)) + epsilon(z) = sqrt(del_B(z)/B_max(z)) where B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. Parameters ---------- z_range : float The axial range (in z-direction, from trap center) over which electron trapping happens. - radial_field_variation : float - Predefined radial variation of magnetic fields, constant for all z's, temporary solution for efficiency calculation. + percent_radial_field_increase : float + Predefined percent radial increase of magnetic field due to radial trap profile, same value for all z's, temporary solution for trapping efficiency calculation. bg_magnetic_field : float The background magnetic field. min_pitch_angle : float @@ -157,22 +157,32 @@ def trapping_efficiency(z_range, radial_field_variation, bg_magnetic_field, min_ Returns ------- mean_efficiency : float - The mean trapping efficiency across the specified trapping range. + The mean trapping efficiency across the trap z-range. Notes ----- The magnetic field profile is computed using the `magnetic_field_flat_harmonic` function, currently it only produces z-profile of the trap without radial variation. - Radial variation is assumed to be constant for all radii, and set during function call. - The mean trapping efficiency is averaged over the region where the field is non-zero. + Radial variation is assumed to be maximum at the edge of the cavity, and only one epsilon_z(r) is calculated at each z (at r = 0), an ideal calculation would evaluate this at different r's. + Maximum percent variation can be set during function call or from configuration. + The mean trapping efficiency is averaged over the region where the trapping field exists. """ + #Warn user if no radial variation + if percent_radial_field_increase == 0: + logger.info("No radial variation given for the electron-trap, try using a fixed efficiency or a non-zero \'percent_radial_variation\'") zs = np.linspace(-z_range, z_range, 100) profiles = [] + #Collect z profile of the magnetic field for z in zs: profiles.append(magnetic_field_flat_harmonic(z, bg_magnetic_field, z_range*2, min_pitch_angle, trap_flat_fraction)) + + #Calculate radial variation from trap depth and percent variation + + radial_field_variation = (max(profiles)-min(profiles))*percent_radial_field_increase/100 - mean_efficiency = np.mean(np.array([np.mean(np.sqrt(radial_field_variation/b_at_z)) for b_at_z in profiles])) + #Calculate mean trapping efficiency using mean of epsilon(z) = sqrt(del_B(z)/B_max(z)) + mean_efficiency = np.mean(np.array([np.sqrt(radial_field_variation/b_at_z) for b_at_z in profiles])) return mean_efficiency @@ -200,7 +210,7 @@ def __init__(self, config_path): #Calculate position dependent trapping efficiency self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, - radial_field_variation = 0.01*mT if not self.MagneticField.radial_variation else self.MagneticField.radial_variation, + percent_radial_field_increase = self.MagneticField.percent_radial_variation, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, trap_flat_fraction = self.MagneticField.trap_flat_fraction, @@ -498,10 +508,10 @@ def syst_magnetic_field(self): frac_uncertainty = self.MagneticField.fraction_uncertainty_on_field_broadening sigma_meanB = self.MagneticField.sigma_meanb sigmaE_meanB = self.BToKeErr(sigma_meanB*B, B) - sigmaE_r = self.MagneticField.sigmae_r + sigmaE_r_non_trap = self.MagneticField.sigmae_r_non_trap_effects sigmaE_theta = self.MagneticField.sigmae_theta sigmaE_phi = self.MagneticField.sigmae_theta - sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r**2 + sigmaE_theta**2 + sigmaE_phi**2) + sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r_non_trap**2 + sigmaE_theta**2 + sigmaE_phi**2) return sigma, frac_uncertainty*sigma else: return 0, 0 From 509350b4de02ec118911f04797d61cc2345f116a Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 10 Dec 2024 16:59:48 -0500 Subject: [PATCH 195/262] code cleanup --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 11f03b84..3f612351 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -188,7 +188,7 @@ def trapping_efficiency(z_range, percent_radial_field_increase, bg_magnetic_fiel -############################################################################### +###############################################################################cd class CavitySensitivity(Sensitivity): """ Documentation: From 3e331d3841d41f58c0ed75f3d4a5682295952ace Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Tue, 10 Dec 2024 16:31:53 -0800 Subject: [PATCH 196/262] add function det_efficiency_tau(self, threshold) --- mermithid/sensitivity/SensitivityCavityFormulas.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 0634be0f..ed984c4f 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -451,6 +451,11 @@ def syst_magnetic_field(self): return sigma, frac_uncertainty*sigma else: return 0, 0 + + def det_efficiency_tau(self, threshold): + tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) + track_duration = self.time_window + return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] # PRINTS From 640d7234e77571956550c7c5856955b1021f19a2 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Tue, 10 Dec 2024 16:33:03 -0800 Subject: [PATCH 197/262] adjust the indent --- mermithid/sensitivity/SensitivityCavityFormulas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index ed984c4f..1a7d10bd 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -453,9 +453,9 @@ def syst_magnetic_field(self): return 0, 0 def det_efficiency_tau(self, threshold): - tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) - track_duration = self.time_window - return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) + track_duration = self.time_window + return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] # PRINTS From d7123df2bdb6eae2aa53e1a60c30322340f2504e Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Tue, 10 Dec 2024 16:42:12 -0800 Subject: [PATCH 198/262] add function background_rate(self, threshold) --- mermithid/sensitivity/SensitivityCavityFormulas.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 1a7d10bd..8609effc 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -456,6 +456,9 @@ def det_efficiency_tau(self, threshold): tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) track_duration = self.time_window return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + + def background_rate(self, threshold): + return chi2(df=2).sf(threshold) # PRINTS From 1b30c8772f48a154a55d94e25f2ad6fe367da64f Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Tue, 10 Dec 2024 16:43:43 -0800 Subject: [PATCH 199/262] import modules ncx2, chi2, and quad --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 8609effc..7f40a2b4 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -7,6 +7,8 @@ CDR (CRES design report, Section 1.3) https://www.overleaf.com/project/5b9314afc673d862fa923d53. ''' import numpy as np +from scipy.stats import ncx2, chi2 +from scipy.integrate import quad from mermithid.misc.Constants_numericalunits import * from mermithid.misc.CRESFunctions_numericalunits import * From b89717e9d2a675bc2f402d5ec48b361d193630d6 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Fri, 13 Dec 2024 12:57:28 -0800 Subject: [PATCH 200/262] How do we know if the result of the rf_background_rate_cavity(self) is per_eV? --- mermithid/sensitivity/SensitivityCavityFormulas.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 7f40a2b4..62066fd0 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -454,14 +454,17 @@ def syst_magnetic_field(self): else: return 0, 0 - def det_efficiency_tau(self, threshold): + def det_efficiency_tau(self): tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) track_duration = self.time_window - return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(self.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] - def background_rate(self, threshold): - return chi2(df=2).sf(threshold) - + def rf_background_rate_cavity(self): + return chi2(df=2).sf(self.threshold) + + def assign_background_rate(self): + self.Experiment.RF_background_rate_per_eV = rf_background_rate_cavity(self) + return None # PRINTS def print_SNRs(self, rho=None): From 2ef644dafc0ecdd2c6ffeac8909ae4ef2935c88c Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 17 Dec 2024 15:03:43 -0500 Subject: [PATCH 201/262] New formula for sigma_noise from determination of f_carrier and f_lsb (lower sideband) --- .../CavitySensitivityCurveProcessor.py | 8 +- .../SensitivityParameterScanProcessor.py | 4 +- .../sensitivity/SensitivityCavityFormulas.py | 192 +++++++++++------- mermithid/sensitivity/SensitivityFormulas.py | 12 +- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 5 files changed, 130 insertions(+), 88 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 35a95a29..45065d26 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -312,7 +312,7 @@ def InternalRun(self): logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) if self.sens_main.FrequencyExtraction.crlb_on_sidebands: - logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) self.sens_main.print_Efficiencies() self.sens_main.print_SNRs(rho_opt) @@ -348,8 +348,8 @@ def InternalRun(self): logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: - logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_ref[i].sigma_K_f_CRLB/eV, self.sens_ref[i].sigma_K_reconstruction/eV)) - + logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_ref[i].sigma_K_noise/eV)) + self.sens_ref[i].print_SNRs(rho_opt_ref) if self.exposure_axis or self.livetime_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") @@ -614,7 +614,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): for rho in self.rhos: limits.append(sens.CL90(Experiment={"number_density": rho})/eV) - resolutions.append(sens.sigma_K_f_CRLB/meV) + resolutions.append(sens.sigma_K_noise/meV) crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 465eeec3..edb519db 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -244,7 +244,7 @@ def InternalRun(self): logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) if self.sens_main.FrequencyExtraction.crlb_on_sidebands: - logger.info("Uncertainty of frequency resolution and energy reconstruction (for pitch angle): {} eV, {} eV".format(self.sens_main.sigma_K_f_CRLB/eV, self.sens_main.sigma_K_reconstruction/eV)) + logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) noise_temp, SNR, track_duration = self.sens_main.print_SNRs(rho_opt) # Store relevant values @@ -392,7 +392,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): for rho in self.rhos: limits.append(sens.CL90(Experiment={"number_density": rho})/eV) - resolutions.append(sens.sigma_K_f_CRLB/meV) + resolutions.append(sens.sigma_K_noise/meV) crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 0634be0f..5102a782 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -32,8 +32,9 @@ def axial_motion(magnetic_field, pitch, trap_length, minimum_trapped_pitch, kin_ # also return the average magnetic field seen by the electron # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable # See LUCKEY write-up for a little more on Talia's "flat fraction" trap model - pitch = pitch/180*np.pi - minimum_trapped_pitch = minimum_trapped_pitch/180*np.pi + + #pitch = pitch/180*np.pi + #minimum_trapped_pitch = minimum_trapped_pitch/180*np.pi # Axial motion: z_w = trap_length/2 @@ -66,18 +67,18 @@ def magnetic_field_flat_harmonic(z, magnetic_field, trap_length, minimum_trapped return magnetic_field*(1+np.heaviside(np.abs(z)-z_w*flat_fraction, 0.5)*(np.abs(z)-z_w*flat_fraction)**2/a**2) -def axial_frequency_box(length, kin_energy, max_pitch_angle=86): - pitch_max = max_pitch_angle/180*np.pi - return (beta(kin_energy)*c0*np.cos(pitch_max)) / (2*length) +def axial_frequency_box(length, kin_energy, max_pitch_angle=86*np.pi/180): + #pitch_max = max_pitch_angle/180*np.pi + return (beta(kin_energy)*c0*np.cos(max_pitch_angle)) / (2*length) -def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, max_pitch_angle=86): +def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, max_pitch_angle=86*np.pi/180, q=0.16): # Because of the different electron trajectories in the trap, # An electron will see a slightly different magnetic field # depending on its position in the trap, especially the pitch angle. # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. #y = (90-max_pitch_angle)/4 - phi_rad = (90-max_pitch_angle)/180*np.pi - return 0.16*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) + phi_rad = (np.pi/2-max_pitch_angle) + return q*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) #return 0.002*y**2*cyclotron_frequency*(10/length_diameter_ratio) # Noise power entering the amplifier, inclding the transmitted noise from the cavity and the reflected noise from the circulator. @@ -135,7 +136,7 @@ class CavitySensitivity(Sensitivity): Documentation: * Phase IV sensitivity document: https://www.overleaf.com/project/5de3e02edd267500011b8cc4 * Talia's sensitivity script: https://3.basecamp.com/3700981/buckets/3107037/documents/2388170839 - * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 + * Nick's CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ def __init__(self, config_path): @@ -149,6 +150,19 @@ def __init__(self, config_path): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") + self.q = 0.16 + if hasattr(self.FrequencyExtraction, "trap_q"): + self.q = self.FrequencyExtraction.trap_q + logger.info("Using configured trap q value") + + self.Jprime_0 = 3.8317 + + #Numbr of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation + self.pitch_steps = 100 + if hasattr(self.FrequencyExtraction, "pitch_steps"): + self.pitch_steps = self.FrequencyExtraction.pitch_steps + logger.info("Using configured pitch_steps value") + self.CavityRadius() self.CavityVolume() self.EffectiveVolume() @@ -163,7 +177,7 @@ def __init__(self, config_path): # CAVITY def CavityRadius(self): axial_mode_index = 1 - self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(3.8317**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.cavity_L_over_D**2)) + self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(self.Jprime_0**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.cavity_L_over_D**2)) return self.cavity_radius def CavityVolume(self): @@ -220,9 +234,9 @@ def CavityPower(self): # from Hamish's atomic calculator #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.FrequencyExtraction.minimum_angle_in_bandwidth, self.Experiment.trap_length, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.FrequencyExtraction.minimum_angle_in_bandwidth, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.cavity_L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W @@ -246,9 +260,9 @@ def CavityLoadedQ(self): # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.FrequencyExtraction.minimum_angle_in_bandwidth, self.Experiment.trap_length, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, + self.FrequencyExtraction.minimum_angle_in_bandwidth, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) required_bw_axialfrequency = max_ax_freq self.required_bw_axialfrequency = required_bw_axialfrequency @@ -320,26 +334,6 @@ def syst_frequency_extraction(self): sigma = self.FrequencyExtraction.Default_Systematic_Smearing delta = self.FrequencyExtraction.Default_Systematic_Uncertainty return sigma, delta - - """ # Cramer-Rao lower bound / how much worse are we than the lower bound - ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor - ts = self.FrequencyExtraction.track_timestep - # "This is apparent in the case of resonant patch antennas and cavities, in which the time scale of the signal onset is set by the Q-factor of the resonant structure." - # You can get it from the finite impulse response of the antennas from HFSS - Gdot = self.FrequencyExtraction.track_onset_rate - - fEndpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) - betae = beta(self.T_endpoint) - Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) - alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope - # quantum limited noise - sigNoise = np.sqrt((2*pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level - Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) - Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts - - # sigma_f from Cramer-Rao lower bound in Hz - sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) - + 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4))))""" endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) @@ -362,64 +356,74 @@ def syst_frequency_extraction(self): # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) # if True: #self.time_window_slope_zero >= self.time_window: # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - sigma_f_CRLB = np.sqrt((self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + self.var_f_c_CRLB = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2 self.best_time_window = self.time_window # non constant slope - self.sigma_f_CRLB_slope_fitted = np.sqrt((20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - if self.CRLB_constant > 10: sigma_f_CRLB = self.sigma_f_CRLB_slope_fitted - self.sigma_f_c_CRLB = sigma_f_CRLB - """ - CRLB_constant = 6 - sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - - - - sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) - - # logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) - self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])]""" - - """# uncertainty in alpha - delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) - # uncetainty in sigma_f in Hz due to uncertainty in alpha - delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" + self.var_f_CRLB_slope_fitted = self.FrequencyExtraction.CRLB_scaling_factor*(20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2 + if self.CRLB_constant > 10: self.var_f_c_CRLB = self.var_f_CRLB_slope_fitted + """ # sigma_f from Cramer-Rao lower bound in eV self.sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*delta_sigma_f_CRLB*c0**2 - + """ + # sigma_f from pitch angle reconstruction if self.FrequencyExtraction.crlb_on_sidebands: + #Calculate noise contribution to uncertainty, including energy correction for pitch angle. + #This comes from section 6.1.9 of the CDR. + tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) - sigma_f_sideband_crlb = np.sqrt((self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor - - # calculate uncertainty of energy correction for pitch angle - var_f0_reconstruction = (sigma_f_sideband_crlb**2+sigma_f_CRLB**2)/self.FrequencyExtraction.sideband_order**2 - max_ax_freq, mean_field, _ = axial_motion(self.MagneticField.nominal_field, - self.FrequencyExtraction.pitch_angle/deg, - self.Experiment.trap_length, - self.FrequencyExtraction.minimum_angle_in_bandwidth/deg, - self.T_endpoint, - flat_fraction=self.MagneticField.trap_flat_fraction) - #max_ax_freq = axial_frequency(self.Experiment.cavity_L_over_D*self.CavityRadius()*2, - # self.T_endpoint, - # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) - # 0.16 is the trap quadratic term. 3.8317 is the first 0 in J'0 - var_f0_reconstruction *= (8 * 0.16 * (3.8317*self.Experiment.cavity_L_over_D / (np.pi * beta(self.T_endpoint)))**2*max_ax_freq/endpoint_frequency)**2*(1/3.0) - sigma_f0_reconstruction = np.sqrt(var_f0_reconstruction) - self.sigma_K_reconstruction = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f0_reconstruction*c0**2 + # (sigmaf_lsb)^2: + var_f_sideband_crlb = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2 - self.sigma_K_f_CRLB = np.sqrt(self.sigma_K_f_CRLB**2 + self.sigma_K_reconstruction**2) + m = self.FrequencyExtraction.sideband_order #For convenience + + #Define the trap parameter p based on the relation between the trap length and the cavity mode + #This p is for a box trap + self.p = np.pi*beta(self.T_endpoint)*self.cavity_radius/self.Jprime_0/self.Experiment.trap_length + + #Define phi_max, corresponding to the minimum pitch angle + phi_max = np.pi/2 - self.FrequencyExtraction.minimum_angle_in_bandwidth + phis = np.linspace(0, phi_max, self.pitch_steps) + #Derivative of f_c0 (frequency corrected to B-field at bottom of the trap) with respect to f_c + dfc0_dfc_array = 0.5*(1 - (1 - 4*self.q*phis/m/self.p + self.q*phis**2)/(1 - self.q*phis**2)) + + #Derivative of f_c0 with respect to f_lsb (lower sideband frequency) + dfc0_dlsb_array = 0.5 - 2*self.q*phis/m/self.p/(1 - self.q*phis**2) + + #Noise variance term from the carrier frequency uncertainty + var_noise_from_fc_array = dfc0_dfc_array**2*self.var_f_c_CRLB + + #Noise variance term from the lower sideband frequency uncertainty + var_noise_from_flsb_array = dfc0_dlsb_array**2*var_f_sideband_crlb + + #Total uncertainty for each pitch angle + sigma_f_noise_array = np.sqrt(var_noise_from_fc_array + var_noise_from_flsb_array) + + #Next, we average over sigma_noise values. + #This is a quadrature sum average, + #reflecting that the detector response function could be constructed by sampling + #from many normal distributions with different standard deviations (sigma_noise_array), + #then finding the standard deviation of the full group of sampled values. + self.sigma_f_noise = np.sqrt(np.sum(sigma_f_noise_array**2)/self.pitch_steps) + #print(self.sigma_f_noise/Hz) + + else: + self.sigma_f_noise = np.sqrt(self.var_f_c_CRLB) + + #Convert uncertainty from frequency to energy + self.sigma_K_noise = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*self.sigma_f_noise*c0**2 # combined sigma_f in eV - sigma_f = np.sqrt(self.sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_smearing**2) + sigma_f = np.sqrt(self.sigma_K_noise**2 + self.FrequencyExtraction.magnetic_field_smearing**2) # delta_sigma_f = np.sqrt((delta_sigma_K_f_CRLB**2 + self.FrequencyExtraction.magnetic_field_uncertainty**2)/2) if self.FrequencyExtraction.usefixeduncertainty: return sigma_f, self.FrequencyExtraction.fixed_relativ_uncertainty*sigma_f else: - raise NotImplementedError("Unvertainty on CRLB for cavity noise calculation is not implemented.") + raise NotImplementedError("Uncertainty on CRLB for cavity noise calculation is not implemented.") def syst_magnetic_field(self): """ @@ -491,7 +495,7 @@ def print_SNRs(self, rho=None): logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) - logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(self.sigma_f_CRLB_slope_fitted/Hz)) + logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(np.sqrt(self.var_f_CRLB_slope_fitted)/Hz)) logger.info("CRLB constant: {}".format(self.CRLB_constant)) return self.noise_temp, SNR_1eV_90deg, track_duration @@ -508,3 +512,41 @@ def print_Efficiencies(self): logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) logger.info("Total efficiency: {}".format(self.effective_volume/self.total_trap_volume)) + + + + +""" # Cramer-Rao lower bound / how much worse are we than the lower bound +ScalingFactorCRLB = self.FrequencyExtraction.CRLB_scaling_factor +ts = self.FrequencyExtraction.track_timestep +# "This is apparent in the case of resonant patch antennas and cavities, in which the time scale of the signal onset is set by the Q-factor of the resonant structure." +# You can get it from the finite impulse response of the antennas from HFSS +Gdot = self.FrequencyExtraction.track_onset_rate + +fEndpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) +betae = beta(self.T_endpoint) +Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) +alpha_approx = fEndpoint * 2 * np.pi * Pe/me/c0**2 # track slope +# quantum limited noise +sigNoise = np.sqrt((2*pi*fEndpoint*hbar*self.FrequencyExtraction.amplifier_noise_scaling+kB*self.FrequencyExtraction.antenna_noise_temperature)/ts) # noise level +Amplitude = np.sqrt(self.FrequencyExtraction.epsilon_collection*Pe) +Nsteps = 1 / (self.Experiment.number_density * self.Te_crosssection*betae*c0*ts) # Number of timesteps of length ts + +# sigma_f from Cramer-Rao lower bound in Hz +sigma_f_CRLB = (ScalingFactorCRLB /(2*np.pi) * sigNoise/Amplitude * np.sqrt(alpha_approx**2/(2*Gdot) ++ 96.*Nsteps/(ts**2*(Nsteps**4-5*Nsteps**2+4))))""" + + +""" +CRLB_constant = 6 +sigma_CRLB_slope_zero = np.sqrt((CRLB_constant*tau_snr_part_length/self.time_window_slope_zero**3)/(2*np.pi)**2)*self.FrequencyExtraction.CRLB_scaling_factor + +sigma_f_CRLB = np.min([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted]) + +# logger.info("CRLB options are: {} , {}".format(sigma_CRLB_slope_zero/Hz, sigma_f_CRLB_slope_fitted/Hz)) +self.best_time_window=[self.time_window_slope_zero, self.time_window][np.argmin([sigma_CRLB_slope_zero, sigma_f_CRLB_slope_fitted])]""" + +"""# uncertainty in alpha +delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) +# uncetainty in sigma_f in Hz due to uncertainty in alpha +delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" \ No newline at end of file diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index d65159f8..795821ba 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -212,7 +212,7 @@ def get_systematics(self): sigma_Miss, delta_sigma_Miss = self.syst_missing_tracks() sigma_Plasma, delta_sigma_Plasma = self.syst_plasma_effects() - labels = ["Thermal Doppler Broadening", "Start Frequency Resolution", "Magnetic Field", "Missing Tracks", "Plasma Effects"] + labels = ["Thermal Doppler Broadening", "Noise (f_carrier and f_lsb)", "Magnetic Field", "Missing Tracks", "Plasma Effects"] sigmas = [sigma_trans, sigma_f, sigma_B, sigma_Miss, sigma_Plasma] deltas = [delta_sigma_trans, delta_sigma_f, delta_sigma_B, delta_sigma_Miss, delta_sigma_Plasma] @@ -240,13 +240,13 @@ def print_systematics(self): sigma_squared += sigma**2 sigma_total = np.sqrt(sigma_squared) print("Total sigma", " "*(np.max([len(l) for l in labels])-len("Total sigma")), "%8.2f"%(sigma_total/meV),) - try: - print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") - except AttributeError: - pass + #try: + # print("(Contribution from axial variation: ", "%8.2f"%(self.sigma_K_reconstruction/meV)," meV)") + #except AttributeError: + # pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.SystSens())/meV), "meV") - logger.info("f_c uncertainty: {} Hz".format(self.sigma_f_c_CRLB/Hz)) + logger.info("Carrier frequency uncertainty: {} Hz".format(np.sqrt(self.var_f_c_CRLB)/Hz)) return np.sqrt(1.64*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 393bd509..934fe149 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_factor_2_back_and_reduced_carrier_power_frac_Dec-6-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_new_sigma_noise_formula_Dec-17-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From c3c5c68785b9dfc3e82325e80721715276f859aa Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 17 Dec 2024 15:45:43 -0500 Subject: [PATCH 202/262] Calculating Larmor power for theta=pi/2, to allow me to remove the pitch_angle parameter from the config files, since it isn't used anywhere else --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 5102a782..a515e749 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -339,7 +339,7 @@ def syst_frequency_extraction(self): endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) # using Pe and alpha (aka slope) from above Pe = self.signal_power #/self.FrequencyExtraction.mode_coupling_efficiency - self.larmor_power = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # currently not used + self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) From 44d7f9030f54f2a3c892371710ed9e72c8dd47d0 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 17 Dec 2024 17:29:47 -0500 Subject: [PATCH 203/262] Made it so that p and q are calculated given the inputted trap info --- .../CavitySensitivityCurveProcessor.py | 3 ++ .../sensitivity/SensitivityCavityFormulas.py | 32 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 45065d26..68b0949b 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -312,6 +312,9 @@ def InternalRun(self): logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) if self.sens_main.FrequencyExtraction.crlb_on_sidebands: + logger.info("Trap p: {}".format(self.sens_main.p)) + logger.info("Trap q: {}".format(self.sens_main.q)) + #print(self.sens_main.q_array) logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) self.sens_main.print_Efficiencies() diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index a515e749..6aa5828c 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -149,11 +149,12 @@ def __init__(self, config_path): if hasattr(self.FrequencyExtraction, "crlb_constant"): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") - - self.q = 0.16 + + """ if hasattr(self.FrequencyExtraction, "trap_q"): self.q = self.FrequencyExtraction.trap_q logger.info("Using configured trap q value") + """ self.Jprime_0 = 3.8317 @@ -247,7 +248,7 @@ def CavityPower(self): frequency(self.T_endpoint, self.MagneticField.nominal_field))) return self.signal_power - + def CavityLoadedQ(self): # Using Wouter's calculation: # Total required bandwidth is the sum of the endpoint region and the axial frequency. @@ -380,14 +381,31 @@ def syst_frequency_extraction(self): m = self.FrequencyExtraction.sideband_order #For convenience - #Define the trap parameter p based on the relation between the trap length and the cavity mode - #This p is for a box trap - self.p = np.pi*beta(self.T_endpoint)*self.cavity_radius/self.Jprime_0/self.Experiment.trap_length - #Define phi_max, corresponding to the minimum pitch angle phi_max = np.pi/2 - self.FrequencyExtraction.minimum_angle_in_bandwidth phis = np.linspace(0, phi_max, self.pitch_steps) + #Define the trap parameter p based on the relation between the trap length and the cavity mode + #This p is for a box trap + self.p_box = np.pi*beta(self.T_endpoint)*self.cavity_radius/self.Jprime_0/self.Experiment.trap_length + + #Now find p for the actual trap that we have + ax_freq_array, mean_field_array, z_t = axial_motion(self.MagneticField.nominal_field, + np.pi/2-phis, self.Experiment.trap_length, + self.FrequencyExtraction.minimum_angle_in_bandwidth, + self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) + fc0_endpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) + p_array = ax_freq_array/fc0_endpoint/phis + self.p = np.mean(p_array[1:]) #Cut out theta=pi/2 (ill defined there) + + #Now calculating q for the trap that we have + #Using the q for the minimum trapped pitch angle + fc_endpoint_min_theta = frequency(self.T_endpoint, mean_field_array[self.pitch_steps-1]) + self.q = 1*(fc_endpoint_min_theta/fc0_endpoint - 1)/(phis[self.pitch_steps-1])**2 + """fc_endpoint_array = frequency(self.T_endpoint, mean_field_array) + self.q_array = 1/phis**2*(fc_endpoint_array/fc0_endpoint - 1) + self.q = np.mean(self.q_array[1:])""" + #Derivative of f_c0 (frequency corrected to B-field at bottom of the trap) with respect to f_c dfc0_dfc_array = 0.5*(1 - (1 - 4*self.q*phis/m/self.p + self.q*phis**2)/(1 - self.q*phis**2)) From 6f53fec78cc1efcac5583b17d951de6eba73c7e0 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 17 Dec 2024 17:30:09 -0500 Subject: [PATCH 204/262] Testing out different flat fractions --- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 934fe149..7c52caba 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_new_sigma_noise_formula_Dec-17-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_fflat_0.75_Dec-17-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From b825e5ad0c785a387f94d483c2c9fc80104b5426 Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 17 Dec 2024 19:02:37 -0500 Subject: [PATCH 205/262] removed radial dependency and restored sigmae_r --- .../sensitivity/SensitivityCavityFormulas.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 3f612351..03483a12 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -130,21 +130,19 @@ def t_effective(t_physical, cyclotron_frequency): return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) # Trapping efficiency from axial field variation. -def trapping_efficiency(z_range, percent_radial_field_increase, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): +def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): """ Calculate the trapping efficiency for a given trap length and flat fraction. The trapping efficiency is computed using the formula: epsilon(z) = sqrt(del_B(z)/B_max(z)) - where B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. + where del_B(z)=B_max(z)-B(z), B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. Parameters ---------- z_range : float The axial range (in z-direction, from trap center) over which electron trapping happens. - percent_radial_field_increase : float - Predefined percent radial increase of magnetic field due to radial trap profile, same value for all z's, temporary solution for trapping efficiency calculation. bg_magnetic_field : float The background magnetic field. min_pitch_angle : float @@ -162,13 +160,9 @@ def trapping_efficiency(z_range, percent_radial_field_increase, bg_magnetic_fiel Notes ----- The magnetic field profile is computed using the `magnetic_field_flat_harmonic` function, currently it only produces z-profile of the trap without radial variation. - Radial variation is assumed to be maximum at the edge of the cavity, and only one epsilon_z(r) is calculated at each z (at r = 0), an ideal calculation would evaluate this at different r's. - Maximum percent variation can be set during function call or from configuration. + No radial field variation was assumed for this calculation. The mean trapping efficiency is averaged over the region where the trapping field exists. """ - #Warn user if no radial variation - if percent_radial_field_increase == 0: - logger.info("No radial variation given for the electron-trap, try using a fixed efficiency or a non-zero \'percent_radial_variation\'") zs = np.linspace(-z_range, z_range, 100) @@ -177,12 +171,11 @@ def trapping_efficiency(z_range, percent_radial_field_increase, bg_magnetic_fiel for z in zs: profiles.append(magnetic_field_flat_harmonic(z, bg_magnetic_field, z_range*2, min_pitch_angle, trap_flat_fraction)) - #Calculate radial variation from trap depth and percent variation - - radial_field_variation = (max(profiles)-min(profiles))*percent_radial_field_increase/100 + #Calculate maximum trapping field along z (Bz_max) + maximum_Bz = max(profiles) #Calculate mean trapping efficiency using mean of epsilon(z) = sqrt(del_B(z)/B_max(z)) - mean_efficiency = np.mean(np.array([np.sqrt(radial_field_variation/b_at_z) for b_at_z in profiles])) + mean_efficiency = np.mean(np.array([np.sqrt((maximum_Bz-b_at_z)/b_at_z) for b_at_z in profiles])) return mean_efficiency @@ -210,7 +203,6 @@ def __init__(self, config_path): #Calculate position dependent trapping efficiency self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, - percent_radial_field_increase = self.MagneticField.percent_radial_variation, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, trap_flat_fraction = self.MagneticField.trap_flat_fraction, @@ -508,10 +500,10 @@ def syst_magnetic_field(self): frac_uncertainty = self.MagneticField.fraction_uncertainty_on_field_broadening sigma_meanB = self.MagneticField.sigma_meanb sigmaE_meanB = self.BToKeErr(sigma_meanB*B, B) - sigmaE_r_non_trap = self.MagneticField.sigmae_r_non_trap_effects + sigmaE_r = self.MagneticField.sigmae_r sigmaE_theta = self.MagneticField.sigmae_theta sigmaE_phi = self.MagneticField.sigmae_theta - sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r_non_trap**2 + sigmaE_theta**2 + sigmaE_phi**2) + sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r**2 + sigmaE_theta**2 + sigmaE_phi**2) return sigma, frac_uncertainty*sigma else: return 0, 0 From 936c0ad640143e8d9f7f55d8a3d5936efa04515d Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Tue, 17 Dec 2024 19:38:21 -0500 Subject: [PATCH 206/262] removed unnecessary variables --- .../sensitivity/SensitivityCavityFormulas.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 03483a12..19792903 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -130,14 +130,14 @@ def t_effective(t_physical, cyclotron_frequency): return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) # Trapping efficiency from axial field variation. -def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5, plotting=False): +def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5): """ Calculate the trapping efficiency for a given trap length and flat fraction. The trapping efficiency is computed using the formula: - epsilon(z) = sqrt(del_B(z)/B_max(z)) - where del_B(z)=B_max(z)-B(z), B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. + epsilon(z) = sqrt(1 - B(z)/B_max(z)) + where B(z) is the magnetic field at position z, and B_max(z) is the maximum magnetic field along the z axis. Parameters ---------- @@ -149,8 +149,6 @@ def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_f Minimum pitch angle to be trapped. trap_flat_fraction : float, optional Flat fraction of the trap. Default is 0.5. - plotting : bool, optional - If True, generates plots of the trapping efficiency. Default is False. Returns ------- @@ -174,8 +172,8 @@ def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_f #Calculate maximum trapping field along z (Bz_max) maximum_Bz = max(profiles) - #Calculate mean trapping efficiency using mean of epsilon(z) = sqrt(del_B(z)/B_max(z)) - mean_efficiency = np.mean(np.array([np.sqrt((maximum_Bz-b_at_z)/b_at_z) for b_at_z in profiles])) + #Calculate mean trapping efficiency using mean of epsilon(z) = sqrt(1-B(z)/B_max(z)) at z = 0 + mean_efficiency = np.mean(np.array([np.sqrt(1-b_at_z/maximum_Bz) for b_at_z in profiles])) return mean_efficiency @@ -205,8 +203,8 @@ def __init__(self, config_path): self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, - trap_flat_fraction = self.MagneticField.trap_flat_fraction, - plotting= True) + trap_flat_fraction = self.MagneticField.trap_flat_fraction + ) self.CavityRadius() self.CavityVolume() From ec594303ab7ceb527445a82440e4121b968ef75f Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 17 Dec 2024 21:14:38 -0500 Subject: [PATCH 207/262] Slightly finer-grained trapping eff calculation (more z steps) --- mermithid/sensitivity/SensitivityCavityFormulas.py | 5 ++--- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index e93717a7..de51e783 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -163,7 +163,7 @@ def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_f The mean trapping efficiency is averaged over the region where the trapping field exists. """ - zs = np.linspace(-z_range, z_range, 100) + zs = np.linspace(-z_range, z_range, 500) profiles = [] #Collect z profile of the magnetic field @@ -175,7 +175,7 @@ def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_f #Calculate mean trapping efficiency using mean of epsilon(z) = sqrt(1-B(z)/B_max(z)) at z = 0 mean_efficiency = np.mean(np.array([np.sqrt(1-b_at_z/maximum_Bz) for b_at_z in profiles])) - + return mean_efficiency @@ -206,7 +206,6 @@ def __init__(self, config_path): min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, trap_flat_fraction = self.MagneticField.trap_flat_fraction ) - """ if hasattr(self.FrequencyExtraction, "trap_q"): self.q = self.FrequencyExtraction.trap_q diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 7c52caba..deceefea 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_fflat_0.75_Dec-17-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_tests_Dec-17-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From 25d42e9fe6808fc5031f0726fc905c238997649e Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Wed, 18 Dec 2024 08:53:59 -0800 Subject: [PATCH 208/262] run methods self.assign_background_rate_from_threshold() and self.assign_detection_efficiency_from_threshold() in the init so the new features are added --- mermithid/sensitivity/SensitivityCavityFormulas.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 62066fd0..7f04cbed 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -156,6 +156,8 @@ def __init__(self, config_path): self.EffectiveVolume() self.PitchDependentTrappingEfficiency() self.CavityPower() + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() #Get trap length from cavity length if not specified if self.Experiment.trap_length == 0: @@ -462,9 +464,15 @@ def det_efficiency_tau(self): def rf_background_rate_cavity(self): return chi2(df=2).sf(self.threshold) - def assign_background_rate(self): + def assign_background_rate_from_threshold(self): self.Experiment.RF_background_rate_per_eV = rf_background_rate_cavity(self) - return None + return self.Experiment.RF_background_rate_per_eV + + def assign_detection_efficiency_from_threshold(self): + effective_volume_before_modified = EffectiveVolume(self) + self.effective_volume = effective_volume_before_modified*det_efficiency_tau(self) + return self.effective_volume + # PRINTS def print_SNRs(self, rho=None): From 36645b8013a7e8240fdf44bf271397d28156c569 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Wed, 18 Dec 2024 18:09:43 -0800 Subject: [PATCH 209/262] after debugging the processor is running with the configure file https://github.com/project8/termite/blob/background_rate_and_detection_efficiency_as_functions_of_SNR/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg --- .../sensitivity/SensitivityCavityFormulas.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 7f04cbed..f794447b 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -143,8 +143,6 @@ class CavitySensitivity(Sensitivity): def __init__(self, config_path): Sensitivity.__init__(self, config_path) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) - - self.CRLB_constant = 12 #self.CRLB_constant = 90 if hasattr(self.FrequencyExtraction, "crlb_constant"): @@ -156,8 +154,12 @@ def __init__(self, config_path): self.EffectiveVolume() self.PitchDependentTrappingEfficiency() self.CavityPower() - self.assign_background_rate_from_threshold() - self.assign_detection_efficiency_from_threshold() + try: + self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() + except: + pass #Get trap length from cavity length if not specified if self.Experiment.trap_length == 0: @@ -457,20 +459,21 @@ def syst_magnetic_field(self): return 0, 0 def det_efficiency_tau(self): - tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) + self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) track_duration = self.time_window - return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(self.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) + return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] def rf_background_rate_cavity(self): - return chi2(df=2).sf(self.threshold) + return chi2(df=2).sf(self.Threshold.threshold) def assign_background_rate_from_threshold(self): - self.Experiment.RF_background_rate_per_eV = rf_background_rate_cavity(self) + self.Experiment.RF_background_rate_per_eV = self.rf_background_rate_cavity() return self.Experiment.RF_background_rate_per_eV def assign_detection_efficiency_from_threshold(self): - effective_volume_before_modified = EffectiveVolume(self) - self.effective_volume = effective_volume_before_modified*det_efficiency_tau(self) + effective_volume_before_modified = self.EffectiveVolume() + self.effective_volume = effective_volume_before_modified*self.det_efficiency_tau() return self.effective_volume From 0d6f1eefbf43c3ceff2d52db593a408ac9490403 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Wed, 18 Dec 2024 18:10:36 -0800 Subject: [PATCH 210/262] after debugging, the script runs for the configuration file https://github.com/project8/termite/blob/background_rate_and_detection_efficiency_as_functions_of_SNR/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg --- test_analysis/Cavity_Sensitivity_analysis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 393bd509..82bb8618 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -168,7 +168,7 @@ "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["red"], # "black"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg"], "comparison_curve_label": [r"Phase IV scenario: 150 MHz"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], @@ -403,6 +403,7 @@ "goals_x_position": 1.2e12, #0.0002 "plot_key_parameters": True } + #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) #sens_curve.Run() From 34e3059dc3ef29e70271094abd64693728dab085 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 20 Dec 2024 13:49:58 -0500 Subject: [PATCH 211/262] Avoiding unecessary sqrt-ing and then squaring of frequency variance from noise --- .../sensitivity/SensitivityCavityFormulas.py | 18 ++++++++++-------- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index de51e783..f4b49976 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -446,6 +446,7 @@ def syst_frequency_extraction(self): self.p_box = np.pi*beta(self.T_endpoint)*self.cavity_radius/self.Jprime_0/self.Experiment.trap_length #Now find p for the actual trap that we have + #Using the average p across the pitch angle range ax_freq_array, mean_field_array, z_t = axial_motion(self.MagneticField.nominal_field, np.pi/2-phis, self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth, @@ -457,10 +458,7 @@ def syst_frequency_extraction(self): #Now calculating q for the trap that we have #Using the q for the minimum trapped pitch angle fc_endpoint_min_theta = frequency(self.T_endpoint, mean_field_array[self.pitch_steps-1]) - self.q = 1*(fc_endpoint_min_theta/fc0_endpoint - 1)/(phis[self.pitch_steps-1])**2 - """fc_endpoint_array = frequency(self.T_endpoint, mean_field_array) - self.q_array = 1/phis**2*(fc_endpoint_array/fc0_endpoint - 1) - self.q = np.mean(self.q_array[1:])""" + self.q = (fc_endpoint_min_theta/fc0_endpoint - 1)/(phis[self.pitch_steps-1])**2 #Derivative of f_c0 (frequency corrected to B-field at bottom of the trap) with respect to f_c dfc0_dfc_array = 0.5*(1 - (1 - 4*self.q*phis/m/self.p + self.q*phis**2)/(1 - self.q*phis**2)) @@ -475,15 +473,14 @@ def syst_frequency_extraction(self): var_noise_from_flsb_array = dfc0_dlsb_array**2*var_f_sideband_crlb #Total uncertainty for each pitch angle - sigma_f_noise_array = np.sqrt(var_noise_from_fc_array + var_noise_from_flsb_array) + var_f_noise_array = var_noise_from_fc_array + var_noise_from_flsb_array #Next, we average over sigma_noise values. #This is a quadrature sum average, #reflecting that the detector response function could be constructed by sampling #from many normal distributions with different standard deviations (sigma_noise_array), #then finding the standard deviation of the full group of sampled values. - self.sigma_f_noise = np.sqrt(np.sum(sigma_f_noise_array**2)/self.pitch_steps) - #print(self.sigma_f_noise/Hz) + self.sigma_f_noise = np.sqrt(np.sum(var_f_noise_array)/self.pitch_steps) else: self.sigma_f_noise = np.sqrt(self.var_f_c_CRLB) @@ -624,4 +621,9 @@ def print_Efficiencies(self): """# uncertainty in alpha delta_alpha = 6*sigNoise/(Amplitude*ts**2) * np.sqrt(10/(Nsteps*(Nsteps**4-5*Nsteps**2+4))) # uncetainty in sigma_f in Hz due to uncertainty in alpha -delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" \ No newline at end of file +delta_sigma_f_CRLB = delta_alpha * alpha_approx *sigNoise**2/(8*np.pi**2*Amplitude**2*Gdot*sigma_f_CRLB*ScalingFactorCRLB**2)""" + + +"""fc_endpoint_array = frequency(self.T_endpoint, mean_field_array) +self.q_array = 1/phis**2*(fc_endpoint_array/fc0_endpoint - 1) +self.q = np.mean(self.q_array[1:])""" \ No newline at end of file diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index deceefea..4c5e8584 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_tests_Dec-17-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_3x-background_Dec-18-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From bab7df33a28b35e51c1ba67a2448b290dc458f42 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 20 Dec 2024 15:20:09 -0500 Subject: [PATCH 212/262] Removed one of two print_SNRs functions, and made remaining function print values for optimum density --- mermithid/sensitivity/SensitivityCavityFormulas.py | 13 ++++++++----- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index e1570fbd..5c2437e4 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -381,7 +381,7 @@ def calculate_tau_snr(self, time_window, power_fraction=1): # end of Wouter's calculation return tau_snr - + """ def print_SNRs(self, rho_opt): tau_snr = self.calculate_tau_snr(self.time_window, sideband_power_fraction=1) logger.info("tau_SNR: {}s".format(tau_snr/s)) @@ -400,7 +400,7 @@ def print_SNRs(self, rho_opt): logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) logger.info("Noise temperature: {}K".format(self.noise_temp/K)) logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) - + """ def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) @@ -549,11 +549,14 @@ def syst_magnetic_field(self): # PRINTS def print_SNRs(self, rho=None): + #logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") + logger.info("SNR parameters:") - if rho != None: - logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") + if rho == None: + track_duration = self.time_window + else: + track_duration = track_length(rho, self.T_endpoint, molecular=(not self.Experiment.atomic)) - track_duration = self.time_window tau_snr_90deg = self.calculate_tau_snr(track_duration, power_fraction=1) #For an example carrier: tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 4c5e8584..4314cf18 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_3x-background_Dec-18-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_LFA-sensitivity-branch_Dec-20-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From 83ff5c183a939fd1555d0c87990e2dfba018f8d9 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 20 Dec 2024 15:22:06 -0500 Subject: [PATCH 213/262] Added descriptive printouts to print_SNRs --- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 5c2437e4..60a05541 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -551,9 +551,10 @@ def syst_magnetic_field(self): def print_SNRs(self, rho=None): #logger.warning("Deprecation warning: This function does not modify the number density in the Experiment namespace. Values printed are for pre-set number density.") - logger.info("SNR parameters:") + logger.info("**SNR parameters**:") if rho == None: track_duration = self.time_window + logger.info("SNR-related parameters are printed for pre-set number density.") else: track_duration = track_length(rho, self.T_endpoint, molecular=(not self.Experiment.atomic)) @@ -590,6 +591,7 @@ def print_SNRs(self, rho=None): logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(np.sqrt(self.var_f_CRLB_slope_fitted)/Hz)) logger.info("CRLB constant: {}".format(self.CRLB_constant)) + logger.info("**Done printing SNR parameters.**") return self.noise_temp, SNR_1eV_90deg, track_duration From 57594d9765b2036309465a65f1e56ca03c3be8b0 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Mon, 23 Dec 2024 12:15:07 -0800 Subject: [PATCH 214/262] Make the configuration use_threshold = True used in the code --- mermithid/sensitivity/SensitivityCavityFormulas.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index f794447b..d93bfb43 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -156,8 +156,9 @@ def __init__(self, config_path): self.CavityPower() try: self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) - self.assign_background_rate_from_threshold() - self.assign_detection_efficiency_from_threshold() + if Threshold.use_threshold == True: + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() except: pass From b90d342ace587278b5157fc162cb9e56803ccdaf Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 25 Dec 2024 16:25:49 -0500 Subject: [PATCH 215/262] Fixed spelling --- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 4314cf18..8543d108 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -1,5 +1,5 @@ """ -Script to make Sensitivty plots for cavity experiments +Script to make sensitivity plots for cavity experiments Author: C. Claessens, T. E. Weiss Date: October 6, 2023 """ From fadd74f7e46df767379f35f8af8ff108e7b243a4 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 25 Dec 2024 16:42:52 -0500 Subject: [PATCH 216/262] Implement three of my comments on PR #73 --- .../sensitivity/SensitivityCavityFormulas.py | 41 +++++++++++-------- mermithid/sensitivity/SensitivityFormulas.py | 2 +- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index cdbeb9ba..7c277984 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -199,10 +199,26 @@ def __init__(self, config_path): self.Jprime_0 = 3.8317 self.CRLB_constant = 12 #self.CRLB_constant = 90 + if hasattr(self.FrequencyExtraction, "crlb_constant"): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") + #If use_threshold is either False or not included in the config file, use the inputted detection efficiency and RF background rate from the config file. + #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. + try: + self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) + if Threshold.use_threshold: + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() + else: + self.detection_efficiency = self.Efficiency.detection_efficiency + self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + except: + self.detection_efficiency = self.Efficiency.detection_efficiency + self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + + #Calculate position dependent trapping efficiency self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, @@ -220,13 +236,6 @@ def __init__(self, config_path): self.CavityVolume() self.EffectiveVolume() self.CavityPower() - try: - self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) - if Threshold.use_threshold == True: - self.assign_background_rate_from_threshold() - self.assign_detection_efficiency_from_threshold() - except: - pass #Get trap length from cavity length if not specified if self.Experiment.trap_length == 0: @@ -277,12 +286,12 @@ def EffectiveVolume(self): #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 - self.fa_cut_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, + self.fa_cut_efficiency = trapping_efficiency(z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.Efficiency.min_pitch_used_in_analysis, trap_flat_fraction = self.MagneticField.trap_flat_fraction )/self.pos_dependent_trapping_efficiency - self.effective_volume = self.total_trap_volume*self.radial_efficiency*self.Efficiency.detection_efficiency*self.fa_cut_efficiency*self.pos_dependent_trapping_efficiency + self.effective_volume = self.total_trap_volume*self.radial_efficiency*self.detection_efficiency*self.fa_cut_efficiency*self.pos_dependent_trapping_efficiency #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) self.effective_volume*=self.Experiment.sri_factor @@ -560,19 +569,19 @@ def det_efficiency_tau(self): self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) track_duration = self.time_window tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) - return quad(lambda tau: ncx2(df=2, nc= tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + return quad(lambda tau: ncx2(df=2, nc=tau/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] def rf_background_rate_cavity(self): - return chi2(df=2).sf(self.Threshold.threshold) + #Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. + return chi2(df=2).sf(self.Threshold.threshold)/(eV*s) def assign_background_rate_from_threshold(self): - self.Experiment.RF_background_rate_per_eV = self.rf_background_rate_cavity() - return self.Experiment.RF_background_rate_per_eV + self.RF_background_rate_per_eV = self.rf_background_rate_cavity() + return self.RF_background_rate_per_eV def assign_detection_efficiency_from_threshold(self): - effective_volume_before_modified = self.EffectiveVolume() - self.effective_volume = effective_volume_before_modified*self.det_efficiency_tau() - return self.effective_volume + self.detection_efficiency = self.det_efficiency_tau() + return self.detection_efficiency # PRINTS diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index b1002d72..55afd22e 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -105,7 +105,7 @@ def BackgroundRate(self): Currently, RF noise and cosmic ray backgrounds are included. Assumes that background rate is constant over considered energy / frequency range.""" self.cosmic_ray_background = self.Experiment.cosmic_ray_bkgd_per_tritium_particle*self.Experiment.number_density*self.effective_volume - self.background_rate = self.Experiment.RF_background_rate_per_eV + self.cosmic_ray_background + self.background_rate = self.RF_background_rate_per_eV + self.cosmic_ray_background return self.background_rate def SignalEvents(self): From db044b27484eb5be74f02ce4e817714e718eb50d Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 25 Dec 2024 18:12:04 -0500 Subject: [PATCH 217/262] Debugged, cleaned code, used cyclotron radius for unusable distance from the wall if it's larger than the inputted distance --- .../misc/CRESFunctions_numericalunits.py | 4 + .../CavitySensitivityCurveProcessor.py | 10 ++- .../sensitivity/SensitivityCavityFormulas.py | 77 +++++++++++-------- test_analysis/Cavity_Sensitivity_analysis.py | 4 +- 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/mermithid/misc/CRESFunctions_numericalunits.py b/mermithid/misc/CRESFunctions_numericalunits.py index 19a327b2..725a9fef 100644 --- a/mermithid/misc/CRESFunctions_numericalunits.py +++ b/mermithid/misc/CRESFunctions_numericalunits.py @@ -28,6 +28,10 @@ def frequency(kin_energy, magnetic_field): # cyclotron frequency return e/(2*np.pi*me)/gamma(kin_energy)*magnetic_field +def cyclotron_radius(freq, kin_energy): + v = beta(kin_energy)*c0 + return v/freq/(2*np.pi) + def wavelength(kin_energy, magnetic_field): return c0/frequency(kin_energy, magnetic_field) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index ed028db1..8e6dfce4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -1,7 +1,7 @@ ''' Calculate sensitivity curve and plot vs. number density, exposure, livetime, or frequency. -Author: C. Claessens, T. Weiss +Author: C. Claessens, T. E. Weiss Date: 06/07/2023 Updated: 12/16/2024 ''' @@ -315,6 +315,7 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) + logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ @@ -357,6 +358,7 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) + logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ @@ -421,9 +423,9 @@ def create_plot(self): if self.atomic_axis and self.molecular_axis: axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" elif self.atomic_axis: - axis_label = r"(Atomic) number density $n\, \, (\mathrm{m}^{-3})$" + axis_label = r"Atom number density $n\, \, (\mathrm{m}^{-3})$" elif self.molecular_axis: - axis_label = r"(Molecular) number density $n\, \, (\mathrm{m}^{-3})$" + axis_label = r"Molecular number density $n\, \, (\mathrm{m}^{-3})$" else: axis_label = r"Number density $n\, \, (\mathrm{m}^{-3})$" @@ -727,8 +729,8 @@ def add_Phase_II_exposure_sens_line(self, sens): sens.print_systematics() sens.print_statistics() - sens.print_Efficiencies() sens.print_SNRs() + sens.print_Efficiencies() logger.info("Phase II sensitivity for exposure {} calculated: {}".format(standard_exposure, sens.sensitivity()/eV**2)) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 7c277984..036b0665 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -194,21 +194,32 @@ class CavitySensitivity(Sensitivity): """ def __init__(self, config_path): Sensitivity.__init__(self, config_path) - self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + #Initialization related to the effective volume: self.Jprime_0 = 3.8317 - self.CRLB_constant = 12 - #self.CRLB_constant = 90 + self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + self.cavity_freq = frequency(self.T_endpoint, self.MagneticField.nominal_field) + self.CavityRadius() + self.CavityVolume() + self.CavityPower() - if hasattr(self.FrequencyExtraction, "crlb_constant"): - self.CRLB_constant = self.FrequencyExtraction.crlb_constant - logger.info("Using configured CRLB constant") + #Get trap length from cavity length if not specified + if self.Experiment.trap_length == 0: + self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + + #Calculate position dependent trapping efficiency + self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, + bg_magnetic_field = self.MagneticField.nominal_field, + min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, + trap_flat_fraction = self.MagneticField.trap_flat_fraction + ) + #Detection efficiency: #If use_threshold is either False or not included in the config file, use the inputted detection efficiency and RF background rate from the config file. #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. try: self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) - if Threshold.use_threshold: + if self.Threshold.use_threshold: self.assign_background_rate_from_threshold() self.assign_detection_efficiency_from_threshold() else: @@ -217,42 +228,35 @@ def __init__(self, config_path): except: self.detection_efficiency = self.Efficiency.detection_efficiency self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + + self.EffectiveVolume() - #Calculate position dependent trapping efficiency - self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, - bg_magnetic_field = self.MagneticField.nominal_field, - min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, - trap_flat_fraction = self.MagneticField.trap_flat_fraction - ) + #Initialization related to the energy resolution: + self.CRLB_constant = 12 + #self.CRLB_constant = 90 + if hasattr(self.FrequencyExtraction, "crlb_constant"): + self.CRLB_constant = self.FrequencyExtraction.crlb_constant + logger.info("Using configured CRLB constant") #Numbr of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation self.pitch_steps = 100 if hasattr(self.FrequencyExtraction, "pitch_steps"): self.pitch_steps = self.FrequencyExtraction.pitch_steps logger.info("Using configured pitch_steps value") - - self.CavityRadius() - self.CavityVolume() - self.EffectiveVolume() - self.CavityPower() - - #Get trap length from cavity length if not specified - if self.Experiment.trap_length == 0: - self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D # CAVITY def CavityRadius(self): axial_mode_index = 1 - self.cavity_radius = c0/(2*np.pi*frequency(self.T_endpoint, self.MagneticField.nominal_field))*np.sqrt(self.Jprime_0**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.cavity_L_over_D**2)) + self.cavity_radius = c0/(2*np.pi*self.cavity_freq)*np.sqrt(self.Jprime_0**2+axial_mode_index**2*np.pi**2/(4*self.Experiment.cavity_L_over_D**2)) return self.cavity_radius def CavityVolume(self): #radius = 0.5*wavelength(self.T_endpoint, self.MagneticField.nominal_field) self.total_cavity_volume = 2*self.cavity_radius*self.Experiment.cavity_L_over_D*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities - logger.info("Frequency: {} MHz".format(round(frequency(self.T_endpoint, self.MagneticField.nominal_field)/MHz, 3))) + logger.info("Frequency: {} MHz".format(round(self.cavity_freq/MHz, 3))) logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Cavity radius: {} cm".format(round(self.cavity_radius/cm, 3))) logger.info("Cavity length: {} cm".format(round(2*self.cavity_radius*self.Experiment.cavity_L_over_D/cm, 3))) @@ -284,8 +288,13 @@ def EffectiveVolume(self): #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - - self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 + cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) + logger.info("Cyclotron radius: {}m".format(cyc_rad/m)) + if self.Efficiency.unusable_dist_from_wall >= cyc_rad: + self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 + else: + logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") + self.radial_efficiency = (self.cavity_radius - cyc_rad)**2/self.cavity_radius**2 self.fa_cut_efficiency = trapping_efficiency(z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.Efficiency.min_pitch_used_in_analysis, @@ -319,7 +328,7 @@ def CavityPower(self): z_t, self.CavityLoadedQ(), 2*self.Experiment.cavity_L_over_D*self.cavity_radius, self.cavity_radius, - frequency(self.T_endpoint, self.MagneticField.nominal_field))) + self.cavity_freq)) return self.signal_power @@ -330,7 +339,7 @@ def CavityLoadedQ(self): #self.loaded_q =1/(0.22800*((90-self.FrequencyExtraction.minimum_angle_in_bandwidth)*np.pi/180)**2+2**2*0.01076**2/(4*0.22800)) - endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + endpoint_frequency = self.cavity_freq #required_bw_axialfrequency = axial_frequency(self.Experiment.cavity_L_over_D*self.CavityRadius()*2, # self.T_endpoint, # self.FrequencyExtraction.minimum_angle_in_bandwidth/deg) @@ -361,7 +370,7 @@ def calculate_tau_snr(self, time_window, power_fraction=1): power_fraction may be used as a carrier or a sideband power fraction, relative to the power of a 90 degree carrier. """ - endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + endpoint_frequency = self.cavity_freq # Cavity coupling self.CavityLoadedQ() @@ -431,7 +440,7 @@ def syst_frequency_extraction(self): return sigma, delta - endpoint_frequency = frequency(self.T_endpoint, self.MagneticField.nominal_field) + endpoint_frequency = self.cavity_freq # using Pe and alpha (aka slope) from above Pe = self.signal_power #/self.FrequencyExtraction.mode_coupling_efficiency self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used @@ -439,7 +448,7 @@ def syst_frequency_extraction(self): self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - self.time_window_slope_zero = abs(frequency(self.T_endpoint, self.MagneticField.nominal_field)-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/self.slope + self.time_window_slope_zero = abs(self.cavity_freq-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/self.slope tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.carrier_power_fraction) tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero, self.FrequencyExtraction.carrier_power_fraction) @@ -489,7 +498,7 @@ def syst_frequency_extraction(self): np.pi/2-phis, self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) - fc0_endpoint = frequency(self.T_endpoint, self.MagneticField.nominal_field) + fc0_endpoint = self.cavity_freq p_array = ax_freq_array/fc0_endpoint/phis self.p = np.mean(p_array[1:]) #Cut out theta=pi/2 (ill defined there) @@ -600,7 +609,7 @@ def print_SNRs(self, rho=None): tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) - eV_bandwidth = np.abs(frequency(self.T_endpoint, self.MagneticField.nominal_field) - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) + eV_bandwidth = np.abs(self.cavity_freq - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) SNR_1eV_90deg = 1/eV_bandwidth/tau_snr_90deg SNR_track_duration_90deg = track_duration/tau_snr_90deg SNR_1ms_90deg = 0.001*s/tau_snr_90deg @@ -638,7 +647,7 @@ def print_Efficiencies(self): if not self.Efficiency.usefixedvalue: # radial and detection efficiency are configured in the config file logger.info("Radial efficiency: {}".format(self.radial_efficiency)) - logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) + logger.info("Detection efficiency: {}".format(self.detection_efficiency)) logger.info("Trapping efficiency: {}".format(self.pos_dependent_trapping_efficiency)) logger.info("Efficiency from axial frequency cut: {}".format(self.fa_cut_efficiency)) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 68bfb7f1..2cdba6c6 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_LFA-sensitivity-branch_Dec-20-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Dec-25-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -168,7 +168,7 @@ "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["red"], # "black"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], "comparison_curve_label": [r"Phase IV scenario: 150 MHz"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], From 59c1b71db0195fff1e980eeb6d6026b483d4dca3 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Thu, 26 Dec 2024 07:58:51 -0800 Subject: [PATCH 218/262] move assign_detection_efficiency_from_threshold() above the two background related functions to improve readdability --- mermithid/sensitivity/SensitivityCavityFormulas.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 036b0665..793fb858 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -580,6 +580,10 @@ def det_efficiency_tau(self): tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) return quad(lambda tau: ncx2(df=2, nc=tau/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + def assign_detection_efficiency_from_threshold(self): + self.detection_efficiency = self.det_efficiency_tau() + return self.detection_efficiency + def rf_background_rate_cavity(self): #Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. return chi2(df=2).sf(self.Threshold.threshold)/(eV*s) @@ -588,9 +592,7 @@ def assign_background_rate_from_threshold(self): self.RF_background_rate_per_eV = self.rf_background_rate_cavity() return self.RF_background_rate_per_eV - def assign_detection_efficiency_from_threshold(self): - self.detection_efficiency = self.det_efficiency_tau() - return self.detection_efficiency + # PRINTS @@ -696,4 +698,4 @@ def print_Efficiencies(self): """fc_endpoint_array = frequency(self.T_endpoint, mean_field_array) self.q_array = 1/phis**2*(fc_endpoint_array/fc0_endpoint - 1) -self.q = np.mean(self.q_array[1:])""" \ No newline at end of file +self.q = np.mean(self.q_array[1:])""" From 0475a3457ac5bdb3aad7673e2a32e6f5ea701846 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Thu, 26 Dec 2024 08:11:15 -0800 Subject: [PATCH 219/262] add comments to the det_efficiency_tau() and rf_background_rate_cavity() methods to reference Rene's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 --- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 793fb858..68832ac4 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -575,6 +575,7 @@ def syst_magnetic_field(self): return 0, 0 def det_efficiency_tau(self): + # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) track_duration = self.time_window tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) @@ -585,7 +586,8 @@ def assign_detection_efficiency_from_threshold(self): return self.detection_efficiency def rf_background_rate_cavity(self): - #Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. + # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + # Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. return chi2(df=2).sf(self.Threshold.threshold)/(eV*s) def assign_background_rate_from_threshold(self): From 04c147f892107c4c09a32ea0667cf5d97e154794 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Thu, 26 Dec 2024 08:40:58 -0800 Subject: [PATCH 220/262] add reference to the antenna paper --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 68832ac4..5724a3dc 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -576,6 +576,7 @@ def syst_magnetic_field(self): def det_efficiency_tau(self): # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) track_duration = self.time_window tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) @@ -587,6 +588,7 @@ def assign_detection_efficiency_from_threshold(self): def rf_background_rate_cavity(self): # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. # Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. return chi2(df=2).sf(self.Threshold.threshold)/(eV*s) From 982c5c79cc02e0966dbcf8365670c5b0834ed36a Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Thu, 26 Dec 2024 09:26:19 -0800 Subject: [PATCH 221/262] adjust the variable names in det_efficiency_track_duration track_duration -> mean_track_duration and tau -> track_duration. Tested that the processor runs. --- mermithid/sensitivity/SensitivityCavityFormulas.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 5724a3dc..e7a71e4e 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -574,16 +574,15 @@ def syst_magnetic_field(self): else: return 0, 0 - def det_efficiency_tau(self): + def det_efficiency_track_duration(self): # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. - self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - track_duration = self.time_window - tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) - return quad(lambda tau: ncx2(df=2, nc=tau/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/track_duration*np.exp(-tau/track_duration), 0, np.infty)[0] + mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) + tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) + return quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.infty)[0] def assign_detection_efficiency_from_threshold(self): - self.detection_efficiency = self.det_efficiency_tau() + self.detection_efficiency = self.det_efficiency_track_duration() return self.detection_efficiency def rf_background_rate_cavity(self): From bd379e2ae0b780dea6357ea87bdf5e93c2461c5b Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sat, 28 Dec 2024 14:54:56 -0500 Subject: [PATCH 222/262] Have consistent order of printing SNR values and efficiency factors --- .../processors/Sensitivity/CavitySensitivityCurveProcessor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 8e6dfce4..1cbd244c 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -309,8 +309,8 @@ def InternalRun(self): #print(self.sens_main.q_array) logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) - self.sens_main.print_Efficiencies() self.sens_main.print_SNRs(rho_opt) + self.sens_main.print_Efficiencies() if self.exposure_axis or self.livetime_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) From 85a6da3770932a2a5be1788c5d699743f9d55ccf Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sat, 28 Dec 2024 15:55:56 -0500 Subject: [PATCH 223/262] Print detection efficiency integration error --- mermithid/sensitivity/SensitivityCavityFormulas.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index e7a71e4e..42a0eaf1 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -579,10 +579,11 @@ def det_efficiency_track_duration(self): # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) - return quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.infty)[0] + result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) + return result, abs_err def assign_detection_efficiency_from_threshold(self): - self.detection_efficiency = self.det_efficiency_track_duration() + self.detection_efficiency, self.abs_err = self.det_efficiency_track_duration() return self.detection_efficiency def rf_background_rate_cavity(self): @@ -653,6 +654,7 @@ def print_Efficiencies(self): # radial and detection efficiency are configured in the config file logger.info("Radial efficiency: {}".format(self.radial_efficiency)) logger.info("Detection efficiency: {}".format(self.detection_efficiency)) + logger.info("Detection efficiency integration error: {}".format(self.abs_err)) logger.info("Trapping efficiency: {}".format(self.pos_dependent_trapping_efficiency)) logger.info("Efficiency from axial frequency cut: {}".format(self.fa_cut_efficiency)) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) From 25a6d76f0da585c28a02f2e2a53bdc0eb10028c8 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Sat, 28 Dec 2024 18:16:36 -0800 Subject: [PATCH 224/262] Change parameter name threshold->detection_threshold. Remove the try-except section in __init__(). Tested that the script runs. --- .../sensitivity/SensitivityCavityFormulas.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 42a0eaf1..bbb04bf0 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -217,18 +217,13 @@ def __init__(self, config_path): #Detection efficiency: #If use_threshold is either False or not included in the config file, use the inputted detection efficiency and RF background rate from the config file. #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. - try: - self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) - if self.Threshold.use_threshold: - self.assign_background_rate_from_threshold() - self.assign_detection_efficiency_from_threshold() - else: - self.detection_efficiency = self.Efficiency.detection_efficiency - self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV - except: - self.detection_efficiency = self.Efficiency.detection_efficiency - self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) + if self.Threshold.use_detection_threshold: + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() + self.detection_efficiency = self.Efficiency.detection_efficiency + self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV self.EffectiveVolume() @@ -579,7 +574,7 @@ def det_efficiency_track_duration(self): # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) - result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) + result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.detection_threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) return result, abs_err def assign_detection_efficiency_from_threshold(self): @@ -590,7 +585,7 @@ def rf_background_rate_cavity(self): # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. # Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. - return chi2(df=2).sf(self.Threshold.threshold)/(eV*s) + return chi2(df=2).sf(self.Threshold.detection_threshold)/(eV*s) def assign_background_rate_from_threshold(self): self.RF_background_rate_per_eV = self.rf_background_rate_cavity() From 61f3b767f5b55f69dfbed339399de1f5b867fe43 Mon Sep 17 00:00:00 2001 From: Yuhao's Mac Date: Sat, 28 Dec 2024 18:29:17 -0800 Subject: [PATCH 225/262] modify the script so only runs on the configuration Config_LFA_Experiment_max_BNL_diam.cfg --- test_analysis/LFA_Sensitivity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_analysis/LFA_Sensitivity.py b/test_analysis/LFA_Sensitivity.py index ba905e66..1e752c52 100644 --- a/test_analysis/LFA_Sensitivity.py +++ b/test_analysis/LFA_Sensitivity.py @@ -61,7 +61,7 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", "plot_path": "./lfa_with_BNL_constraints_sensitivity_vs_exposure_curve_PhaseIV.pdf", "exposure_axis": True, # optional @@ -81,8 +81,8 @@ "main_curve_upper_label": r"Phase III scenario: 1 GHz, L/D=9", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", "goals": {"Phase III (0.4 eV)": (0.4**2/1.64), "Phase IV (0.04 eV)": (0.04**2/1.64)}, - "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_500MHz.cfg", + "comparison_curve": False, + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg"], # "/termite/sensitivity_config_files/Config_atomic_find_factor_22_Experiment_conservative.cfg"], #"comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment_conservative.cfg", From ba4a8954683115bfaffee850a1de2cd5b3960c24 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 30 Dec 2024 12:58:00 -0500 Subject: [PATCH 226/262] Preparing to test Yu-Hao's new changes --- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 2cdba6c6..ecb6d9a1 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Dec-25-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Dec-30-2024.pdf", # optional "figsize": (7.0,6), "fontsize": 15, From cf8b2e96df98a489d430e0f823c926492dc09cc4 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 31 Dec 2024 10:44:30 -0500 Subject: [PATCH 227/262] Fixed problem of detection efficiency not being re-calculated for each density in the density optimization --- .../CavitySensitivityCurveProcessor.py | 14 +++-- .../sensitivity/SensitivityCavityFormulas.py | 53 ++++++++++--------- mermithid/sensitivity/SensitivityFormulas.py | 1 + test_analysis/Cavity_Sensitivity_analysis.py | 8 +-- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 1cbd244c..d4050143 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -174,7 +174,7 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 1000)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 150)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz @@ -494,7 +494,7 @@ def create_plot(self): ax.set_xlabel(axis_label) self.kp_ax[0].set_ylabel('Resolution (meV)') - self.kp_ax[1].set_ylabel('Track analysis length (ms)') + self.kp_ax[1].set_ylabel('Track analysis duration (ms)') elif self.frequency_axis: @@ -521,7 +521,8 @@ def add_track_length_axis(self): if self.atomic_axis: ax2 = self.ax.twiny() ax2.set_xscale("log") - ax2.set_xlabel("(Atomic) track length (s)") + #ax2.set_xlabel("(Atomic) track duration (s)") + ax2.set_xlabel("Track duration (s)") if self.sens_main_is_atomic: ax2.set_xlim(self.sens_main.track_length(self.rhos[0])/s, @@ -535,7 +536,7 @@ def add_track_length_axis(self): if self.molecular_axis: ax3 = self.ax.twiny() ax3.set_xscale("log") - ax3.set_xlabel("(Molecular) track length (s)") + ax3.set_xlabel("(Molecular) track duration (s)") if self.atomic_axis: ax3.spines["top"].set_position(("axes", 1.2)) @@ -620,6 +621,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_window = [] crlb_max_window = [] crlb_slope_zero_window = [] + det_effs = [] temp_rho = deepcopy(sens.Experiment.number_density) for rho in self.rhos: @@ -628,7 +630,9 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) - + det_effs.append(self.sens_main.detection_efficiency) + + print(det_effs) sens.Experiment.number_density = temp_rho self.ax.plot(self.rhos*m**3, limits, **kwargs) logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index bbb04bf0..28030a93 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -219,13 +219,13 @@ def __init__(self, config_path): #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) - if self.Threshold.use_detection_threshold: - self.assign_background_rate_from_threshold() - self.assign_detection_efficiency_from_threshold() - self.detection_efficiency = self.Efficiency.detection_efficiency - self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + self.EffectiveVolume() - + logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3), 2)) + logger.info("Total trap volume {} m^3".format(round(self.total_trap_volume/m**3), 2)) + logger.info("Cyclotron radius: {}m".format(self.cyc_rad/m)) + if self.use_cyc_rad: + logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") #Initialization related to the energy resolution: self.CRLB_constant = 12 @@ -264,11 +264,6 @@ def CavityVolume(self): def TrapVolume(self): # Total volume of the electron traps in all cavities self.total_trap_volume = self.Experiment.trap_length*np.pi*(self.cavity_radius)**2*self.Experiment.n_cavities - - logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3))) - logger.info("Trap length: {} cm".format(round(self.Experiment.trap_length/cm, 3))) - logger.info("Total trap volume {} m^3".format(round(self.total_trap_volume/m**3))) - return self.total_trap_volume @@ -278,33 +273,39 @@ def EffectiveVolume(self): if self.Efficiency.usefixedvalue: self.effective_volume = self.total_trap_volume * self.Efficiency.fixed_efficiency else: - # radial and detection efficiency are configured in the config file - #logger.info("Radial efficiency: {}".format(self.Efficiency.radial_efficiency)) - #logger.info("Detection efficiency: {}".format(self.Efficiency.detection_efficiency)) - #logger.info("Pitch angle efficiency: {}".format(self.PitchDependentTrappingEfficiency())) - #logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) - logger.info("Cyclotron radius: {}m".format(cyc_rad/m)) - if self.Efficiency.unusable_dist_from_wall >= cyc_rad: + #Detection efficiency + if self.Threshold.use_detection_threshold: + self.assign_background_rate_from_threshold() + self.assign_detection_efficiency_from_threshold() + else: + self.detection_efficiency = self.Efficiency.detection_efficiency + self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + + #print("TEMP detection efficiency: {}".format(self.detection_efficiency)) + #Radial efficiency + self.cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) + if self.Efficiency.unusable_dist_from_wall >= self.cyc_rad: self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 + self.use_cyc_rad = False else: - logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") - self.radial_efficiency = (self.cavity_radius - cyc_rad)**2/self.cavity_radius**2 + self.radial_efficiency = (self.cavity_radius - self.cyc_rad)**2/self.cavity_radius**2 + self.use_cyc_rad = True + + #Efficiency from a cut during analysis on the axial frequency self.fa_cut_efficiency = trapping_efficiency(z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.Efficiency.min_pitch_used_in_analysis, trap_flat_fraction = self.MagneticField.trap_flat_fraction )/self.pos_dependent_trapping_efficiency + + #The effective volume includes the three efficiency factors above, as well as the trapping efficiency self.effective_volume = self.total_trap_volume*self.radial_efficiency*self.detection_efficiency*self.fa_cut_efficiency*self.pos_dependent_trapping_efficiency - #logger.info("Total efficiency: {}".format(self.effective_volume/self.total_volume)) + # The "signal rate improvement" factor can be toggled to test the increase in statistics required to reach some sensitivity self.effective_volume*=self.Experiment.sri_factor - - # for parent SignalRate function - # self.Experiment.v_eff = self.effective_volume - return self.effective_volume + def BoxTrappingEfficiency(self): self.box_trapping_efficiency = np.cos(self.FrequencyExtraction.minimum_angle_in_bandwidth) return self.box_trapping_efficiency diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 55afd22e..f52cfcc7 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -89,6 +89,7 @@ def __init__(self, config_path): # SENSITIVITY def SignalRate(self): """signal events in the energy interval before the endpoint, scale with DeltaE**3""" + self.EffectiveVolume() signal_rate = self.Experiment.number_density*self.effective_volume*self.last_1ev_fraction/self.tau_tritium if not self.Experiment.atomic: if hasattr(self.Experiment, 'gas_fractions'): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index ecb6d9a1..03583adc 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -162,9 +162,9 @@ "track_length_axis": True, "cavity": True, "y_limits": [2e-2, 4], - "density_range": [1e13,1e19], - "main_curve_upper_label": r"Phase III scenario: 560 MHz", #Phase III scenario: 1 GHz", - "goals": {"Phase III (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "density_range": [1e14,3e18], + "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", + "goals": {"LFA (0.3 eV)": 0.3, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["red"], # "black"], @@ -176,7 +176,7 @@ "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 3e13, #0.0002 + "goals_x_position": 1.3e14, #0.0002 "plot_key_parameters": True } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") From 3aff343b733512df9b21ac630f270546b913bc13 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 8 Jan 2025 11:40:13 -0500 Subject: [PATCH 228/262] Added 2 more x-axis exposure-type options, added option to mark an operating density in sens vs density plots, added option to not optimize density in sens vs exposure-type plots, swapped the left and right y-axes in sens vs exposure-type plots, added single Phase IV cavity to example plots, accounted properly for the case where the threshold isn't specified (fixed efficiency) or the trap length isn't specified, improved plot formatting --- .../CavitySensitivityCurveProcessor.py | 341 ++++++++++-------- .../sensitivity/SensitivityCavityFormulas.py | 43 ++- test_analysis/Cavity_Sensitivity_analysis.py | 42 +-- 3 files changed, 239 insertions(+), 187 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index d4050143..e6bc03eb 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -3,7 +3,7 @@ Author: C. Claessens, T. E. Weiss Date: 06/07/2023 -Updated: 12/16/2024 +Updated: January 2025 ''' from __future__ import absolute_import @@ -65,7 +65,7 @@ def InternalConfigure(self, params): self.main_curve_color = reader.read_param(params, 'main_curve_color', "darkblue") # options - #self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) + self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) self.optimize_comparison_density = reader.read_param(params, 'optimize_comparison_density', True) self.verbose = reader.read_param(params, 'verbose', True) self.comparison_curve = reader.read_param(params, 'comparison_curve', False) @@ -83,6 +83,8 @@ def InternalConfigure(self, params): self.frequency_axis = reader.read_param(params, "frequency_axis", False) self.exposure_axis = reader.read_param(params, "exposure_axis", False) self.livetime_axis = reader.read_param(params, "livetime_axis", False) + self.ncavities_livetime_axis = reader.read_param(params, "ncavities_livetime_axis", False) + self.ncav_eff_time_axis = reader.read_param(params, "ncav_eff_time_axis", False) self.track_length_axis = reader.read_param(params, 'track_length_axis', True) self.atomic_axis = reader.read_param(params, 'atomic_axis', False) self.molecular_axis = reader.read_param(params, 'molecular_axis', False) @@ -106,6 +108,7 @@ def InternalConfigure(self, params): self.comparison_label_x_position = reader.read_param(params, 'label_x_position', 5e16) self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) + self.add_point_at_configured_density = reader.read_param(params, "add_point_at_configured_density", True) self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) self.add_1year_1cav_point_to_last_ref = reader.read_param(params, "add_1year_1cav_point_to_last_ref", False) @@ -118,12 +121,11 @@ def InternalConfigure(self, params): elif self.frequency_axis: self.add_sens_line = self.add_frequency_sens_line logger.info("Plotting sensitivity vs. frequency") - elif self.exposure_axis or self.livetime_axis: + if self.optimize_main_density==False or self.optimize_comparison_density==False: + logger.warning("You told me not to optimize the density, but I am doing so anyway! We need to fix this.") + elif self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis: self.add_sens_line = self.add_exposure_sens_line logger.info("Plotting sensitivity vs. exposure or livetime") - #elif self.livetime_axis: - # self.add_sens_line = self.add_exposure_sens_line(livetime_plot=True) - # logger.info("Plotting sensitivity vs. livetime") else: raise ValueError("No axis specified") @@ -174,11 +176,15 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 150)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 50)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz + ncav_temp = self.sens_main.Experiment.n_cavities + self.ncavities_livetime_range = [ncav_temp*self.year_range[0], ncav_temp*self.year_range[1]] + self.ncav_eff_time_range = [ncav_temp*self.year_range[0]*0.001, ncav_temp*self.year_range[1]*0.1] + return True @@ -186,41 +192,10 @@ def InternalConfigure(self, params): def InternalRun(self): logger.info("Systematics before density optimization:") self.sens_main.print_systematics() + self.sens_with_configured_density = self.sens_main.CL90() + logger.info("Sensitivity before density optimization: {} eV".format(self.sens_with_configured_density/eV)) logger.info("Number density: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) logger.info("Corresponding track length: {} s".format(self.sens_main.track_length(self.sens_main.Experiment.number_density)/s)) - - if self.make_key_parameter_plots: - - # First key parameter plot: Stat and Syst vs. density - - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] - - for n in self.rhos: - temp_rho = deepcopy(self.sens_main.Experiment.number_density) - self.sens_main.Experiment.number_density = n - labels, sigmas, deltas = self.sens_main.get_systematics() - sigma_startf.append(sigmas[1]) - stat_on_mbeta2.append(self.sens_main.StatSens()) - syst_on_mbeta2.append(self.sens_main.SystSens()) - self.sens_main.Experiment.number_density = temp_rho - - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) - fig = plt.figure() - plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') - plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") - plt.legend() - plt.tight_layout() - plt.savefig("stat_and_syst_vs_density.pdf") - - fig = plt.figure() - plt.loglog(self.rhos*m**3, sigma_startf/eV) - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") - plt.tight_layout() - plt.savefig("resolution_from_CRLB_vs_density.pdf") - # create main plot self.create_plot() @@ -241,12 +216,17 @@ def InternalRun(self): for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) self.add_goal(value, key) - + + if self.density_axis and self.add_point_at_configured_density: + self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density/eV], marker="s", s=25, color='b', label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) + # optimize density - limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt = np.argmin(limit) - rho_opt = self.rhos[opt] - self.sens_main.Experiment.number_density = rho_opt + if self.optimize_main_density: + limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt = np.argmin(limit) + rho_opt = self.rhos[opt] + self.sens_main.Experiment.number_density = rho_opt + # if B is list plot line for each B if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): @@ -259,7 +239,11 @@ def InternalRun(self): self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[a] * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV if self.livetime_axis: - self.add_sens_line(self.sens_main, livetime_plot=True, color=color) + self.add_sens_line(self.sens_main, exposure_type="livetime", color=color) + elif self.ncavities_livetime_axis: + self.add_sens_line(self.sens_main, exposure_type="ncavities_livetime", color=color) + elif self.ncav_eff_time_axis: + self.add_sens_line(self.sens_main, exposure_type="ncav_eff_time", color=color) else: self.add_sens_line(self.sens_main, color=color) #print("sigmae_theta_r:", self.sens_main.MagneticField.sigmae_r/eV) @@ -268,19 +252,17 @@ def InternalRun(self): self.add_text(self.label_x_position, self.lower_label_y_position, self.main_curve_lower_label, color="darkred") else: - #sig = self.sens_main.BToKeErr(self.sens_main.MagneticField.nominal_field*self.B_error[a], self.sens_main.MagneticField.nominal_field) - #self.sens_main.MagneticField.usefixedvalue = True - #self.sens_main.MagneticField.default_systematic_smearing = sig - #self.sens_main.MagneticField.default_systematic_uncertainty = 0.05*sig if self.configure_sigma_theta_r: self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV if self.livetime_axis: - self.add_sens_line(self.sens_main, livetime_plot=True, color=self.main_curve_color, label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, exposure_type="livetime", color=self.main_curve_color, label=self.main_curve_upper_label) + elif self.ncavities_livetime_axis: + self.add_sens_line(self.sens_main, exposure_type="ncavities_livetime", color=self.main_curve_color, label=self.main_curve_upper_label) + elif self.ncav_eff_time_axis: + self.add_sens_line(self.sens_main, exposure_type="ncav_eff_time", color=self.main_curve_color, label=self.main_curve_upper_label) else: - self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label) - #self.add_text(self.label_x_position, self.upper_label_y_position, self.main_curve_upper_label, color='blue') - + self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label, zorder=2) # PRINT OPTIMUM RESULTS @@ -292,10 +274,12 @@ def InternalRun(self): self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV - logger.info('Main curve:') - # set optimum density back - #self.sens_main.CL90(Experiment={"number_density": rho_opt}) - rho = self.sens_main.Experiment.number_density + if self.optimize_main_density: + logger.info('MAIN CURVE at optimum density:') + rho = rho_opt + else: + logger.info('MAIN CURVE at configured density (not optimum):') + rho = self.sens_main.Experiment.number_density logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_main.effective_volume/(m**3), rho*(m**3))) logger.info("Loaded Q: {}".format(self.sens_main.loaded_q)) logger.info("Axial frequency for minimum detectable angle: {} MHz".format(self.sens_main.required_bw_axialfrequency/MHz)) @@ -309,15 +293,16 @@ def InternalRun(self): #print(self.sens_main.q_array) logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) - self.sens_main.print_SNRs(rho_opt) + self.sens_main.print_SNRs(rho) self.sens_main.print_Efficiencies() - if self.exposure_axis or self.livetime_axis: + + if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") - logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho_opt})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) + logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) + logger.info('T2 in Veff: {}'.format(rho*self.sens_main.effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) - logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* + logger.info('Total signal: {}'.format(rho*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* @@ -328,23 +313,53 @@ def InternalRun(self): self.sens_main.print_statistics() self.sens_main.print_systematics() - # Optimize comparison curves over density - if self.comparison_curve: + if self.make_key_parameter_plots: + # First key parameter plot: Stat and Syst vs. density + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + for n in self.rhos: + temp_rho = deepcopy(self.sens_main.Experiment.number_density) + self.sens_main.Experiment.number_density = n + labels, sigmas, deltas = self.sens_main.get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_main.StatSens()) + syst_on_mbeta2.append(self.sens_main.SystSens()) + self.sens_main.Experiment.number_density = temp_rho + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig("stat_and_syst_vs_density.pdf") + + fig = plt.figure() + plt.loglog(self.rhos*m**3, sigma_startf/eV) + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") + plt.tight_layout() + plt.savefig("resolution_from_CRLB_vs_density.pdf") + + # Optimize comparison curves over density + if self.comparison_curve: for i in range(len(self.sens_ref)): - if self.optimize_comparison_density: limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt_ref = np.argmin(limit_ref) - rho_opt_ref = self.rhos[opt_ref] - #self.sens_ref[i].Experiment.number_density = rho_opt_ref - self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) - - - + limit2 = np.argmin(limit_ref) + rho_opt_ref = self.rhos[limit2] + self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + else: + limit2 = self.sens_ref[i].CL90()/eV - logger.info('Comparison curve:') - logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho_opt_ref*(m**3))) + if self.optimize_comparison_density: + logger.info('COMPARISON CURVE at optimum density:') + rho2 = rho_opt_ref + else: + logger.info('COMPARISON CURVE at configured density (not optimum):') + rho2 = self.sens_main.Experiment.number_density + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho2*(m**3))) logger.info("Loaded Q: {}".format(self.sens_ref[i].loaded_q)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_ref[i].signal_power/self.sens_ref[i].larmor_power)) @@ -352,19 +367,19 @@ def InternalRun(self): if self.sens_ref[i].FrequencyExtraction.crlb_on_sidebands: logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_ref[i].sigma_K_noise/eV)) - self.sens_ref[i].print_SNRs(rho_opt_ref) + self.sens_ref[i].print_SNRs(rho2) self.sens_ref[i].print_Efficiencies() - if self.exposure_axis or self.livetime_axis: + if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") - logger.info('CL90 limit: {}'.format(self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref})/eV)) - logger.info('T2 in Veff: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume)) + logger.info('CL90 limit: {}'.format(limit2)) + logger.info('T2 in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) - logger.info('Total signal: {}'.format(rho_opt_ref*self.sens_ref[i].effective_volume* + logger.info('Total signal: {}'.format(rho2*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) logger.info('Signal in last eV: {}'.format(self.sens_ref[i].last_1ev_fraction*eV**3* - rho_opt_ref*self.sens_ref[i].effective_volume* + rho2*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ self.sens_ref[i].tau_tritium*2)) @@ -374,8 +389,6 @@ def InternalRun(self): self.add_comparison_curve(label=self.comparison_curve_label) #self.add_arrow(self.sens_main) - - # save plot self.save(self.plot_path) @@ -423,7 +436,7 @@ def create_plot(self): if self.atomic_axis and self.molecular_axis: axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" elif self.atomic_axis: - axis_label = r"Atom number density $n\, \, (\mathrm{m}^{-3})$" + axis_label = r"Atom number density in electron trap$\, \, (\mathrm{m}^{-3})$" elif self.molecular_axis: axis_label = r"Molecular number density $n\, \, (\mathrm{m}^{-3})$" else: @@ -447,40 +460,36 @@ def create_plot(self): self.ax2.set_yscale("log") self.ax2.set_ylim(self.ylim) - elif self.exposure_axis: - logger.info("Adding exposure axis") - ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) - #ax.tick_params(axis='x', which='minor', bottom=True) - #ax.tick_params(axis='y', which='minor', left=True) - axis_label = r"Effective Volume $\times$ Time (m$^3$y)" #r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" - - ax.set_xlabel(axis_label) - #ax.set_ylim((np.array(self.ylim)**2/np.sqrt(1.64))) - ax.set_ylim((np.array(self.ylim)**2/1.64)) - ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") - - self.ax2 = ax.twinx() - self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") - self.ax2.set_yscale("log") - self.ax2.set_ylim(self.ylim) + elif self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: + if self.exposure_axis: + logger.info("Adding exposure axis") + ax.set_xlim(self.exposure_range[0], self.exposure_range[-1]) + axis_label = r"Effective Volume $\times$ Time (m$^3$y)" #r"Efficiency $\times$ Volume $\times$ Time (m$^3$y)" + elif self.livetime_axis: + logger.info("Adding livetime axis") + ax.set_xlim(self.year_range[0], self.year_range[-1]) + axis_label = r"Livetime (years)" + elif self.ncavities_livetime_axis: + logger.info("Adding ncavities*livetime axis") + ax.set_xlim(self.ncavities_livetime_range[0], self.ncavities_livetime_range[-1]) + axis_label = r"Number of Cavities $\times$ Livetime (years)" + elif self.ncav_eff_time_axis: + logger.info("Adding ncavities*efficiency*livetime axis") + ax.set_xlim(self.ncav_eff_time_range[0], self.ncav_eff_time_range[-1]) + axis_label = r"Number of Cavities $\times$ Efficiency $\times$ Livetime (years)" - else: - logger.info("Adding livetime axis") - ax.set_xlim(self.year_range[0], self.year_range[-1]) - axis_label = r"Livetime (years)" - ax.set_xlabel(axis_label) - ax.set_ylim((np.array(self.ylim)**2/1.64)) - ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + ax.set_ylim(self.ylim) self.ax2 = ax.twinx() - self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") + self.ax2.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") self.ax2.set_yscale("log") - self.ax2.set_ylim(self.ylim) + self.ax2.set_ylim((np.array(self.ylim)**2/1.64)) + if self.make_key_parameter_plots: - if self.density_axis: self.kp_fig, self.kp_ax = plt.subplots(1,2, figsize=(10,6)) @@ -570,19 +579,23 @@ def add_magnetic_field_axis(self): def add_comparison_curve(self, label, color='k'): for a in range(len(self.sens_ref)): - if self.livetime_axis == True: - limits = self.add_sens_line(self.sens_ref[a], livetime_plot=True, plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) + if self.livetime_axis: + limits = self.add_sens_line(self.sens_ref[a], exposure_type="livetime", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) + elif self.ncavities_livetime_axis: + limits = self.add_sens_line(self.sens_ref[a], exposure_type="ncavities_livetime", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) + elif self.ncav_eff_time_axis: + limits = self.add_sens_line(self.sens_ref[a], exposure_type="ncav_eff_time", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) else: - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=True, color=self.comparison_curve_colors[a], label=label[a]) + limits = self.add_sens_line(self.sens_ref[a], plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a], zorder=5) #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) - if not self.density_axis and self.add_1year_1cav_point_to_last_ref: - logger.info("Going to add single exposure point") - self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) - #self.ax.axhline(0.04, color="gray", ls="--") - #self.ax.text(3e14, 0.044, "Phase IV (40 meV)") + if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: + if self.add_1year_1cav_point_to_last_ref: + logger.info("Going to add single exposure point") + self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) def add_arrow(self, sens): + #I am not sure why the two line below are here if not hasattr(self, "opt_ref"): self.add_comparison_curve() @@ -612,8 +625,8 @@ def get_relative(val, axis): """ def add_goal(self, value, label): - self.ax.axhline(value, color="gray", ls="--") - self.ax.text(self.goal_x_pos, self.goals_y_rel_position*value, label) + self.ax.axhline(value, color="gray", ls="--", zorder=1) + self.ax.text(self.goal_x_pos, self.goals_y_rel_position*value, label, fontsize=self.fontsize - 3) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -621,7 +634,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_window = [] crlb_max_window = [] crlb_slope_zero_window = [] - det_effs = [] + #det_effs = [] temp_rho = deepcopy(sens.Experiment.number_density) for rho in self.rhos: @@ -630,9 +643,9 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) - det_effs.append(self.sens_main.detection_efficiency) + #det_effs.append(self.sens_main.detection_efficiency) - print(det_effs) + #print(det_effs) sens.Experiment.number_density = temp_rho self.ax.plot(self.rhos*m**3, limits, **kwargs) logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) @@ -654,7 +667,8 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" livetime_for_label = round(livetime/year, 1) if sens.Experiment.atomic: - label="Atomic, reaching pilot-T target".format(n_cavities, livetime_for_label) + #label="Atomic, reaching pilot-T target".format(n_cavities, livetime_for_label) + label="1 Phase IV cavity, 1 year of livetime".format(n_cavities, livetime_for_label) else: label="Molecular, {} cavity, {} year".format(n_cavities, livetime_for_label) @@ -671,51 +685,73 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" #rho_opt = self.rhos[opt] #sens.Experiment.number_density = rho_opt - - limit = sens.sensitivity()/eV**2 + #sigma_mbeta2 = sens.sensitivity()/eV**2 standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year #*exposure_fraction #lt = standard_exposure/sens.EffectiveVolume() #sens.Experiment.livetime = lt - limit = sens.sensitivity()/eV**2 + limit = sens.CL90()/eV if self.exposure_axis: self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) - logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, np.sqrt(1.64*limit))) - if self.livetime_axis: + logger.info("Exposure and mass limit for single point: {}, {}".format(standard_exposure, limit)) + elif self.livetime_axis: self.ax.scatter([sens.Experiment.livetime/year], [limit], marker="s", s=25, color=color, label=label, zorder=10) - logger.info("Livetime and mass limit for single point: {}, {}".format(sens.Experiment.livetime/year, np.sqrt(1.64*limit))) + logger.info("Livetime and mass limit for single point: {}, {}".format(sens.Experiment.livetime/year, limit)) + elif self.ncavities_livetime_axis: + ncav_time = sens.Experiment.n_cavities*sens.Experiment.livetime/year + self.ax.scatter([ncav_time], [limit], marker="s", s=25, color=color, label=label, zorder=10) + logger.info("Ncavities*livetime and mass limit for single point: {}, {}".format(ncav_time, limit)) + elif self.ncav_eff_time_axis: + ncav_eff_time = sens.effective_volume/sens.total_trap_volume*sens.Experiment.livetime/year + self.ax.scatter([ncav_eff_time], [limit], marker="s", s=25, color=color, label=label, zorder=10) + logger.info("Ncavities*efficiency*livetime and mass limit for single point: {}, {}".format(ncav_eff_time, limit)) sens.print_statistics() sens.print_systematics() - def add_exposure_sens_line(self, sens, livetime_plot=False, plot_key_params=False, **kwargs): + def add_exposure_sens_line(self, sens, exposure_type=None, plot_key_params=False, **kwargs): + #Optimization happens before calling this function, so I'm commenting out the below + """ sigma_mbeta = [sens.sensitivity(Experiment={"number_density": rho})/eV**2 for rho in self.rhos] opt = np.argmin(sigma_mbeta) rho_opt = self.rhos[opt] sens.Experiment.number_density = rho_opt + """ + #logger.info("Years: {}".format(sens.Experiment.livetime/year)) - #logger.info("Optimum density: {} /m^3".format(rho_opt*m**3)) - logger.info("Years: {}".format(sens.Experiment.livetime/year)) - - sigma_mbetas = [] - years = [] - if livetime_plot: - self.ax.scatter([sens.Experiment.livetime/year], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + #Calcuating sensitivity for the exposure in the config file + limit_inputted_exposure = sens.CL90()/eV + limits = [] + if exposure_type=="livetime": + #self.ax.scatter([sens.Experiment.livetime/year], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) for lt in self.years: sens.Experiment.livetime = lt - sigma_mbetas.append(sens.sensitivity()/eV**2) - self.ax.plot(self.years/year, sigma_mbetas, color=kwargs["color"]) + #limits.append(sens.sensitivity()/eV**2) + limits.append(sens.CL90()/eV) + self.ax.plot(self.years/year, limits, color=kwargs["color"]) + elif exposure_type=="ncavities_livetime": + ncavities_livetime = [] + for lt in self.years: + sens.Experiment.livetime = lt + ncavities_livetime.append(self.Experiment.n_cavities*lt) + #sigma_mbetas.append(sens.sensitivity()/eV**2) + limits.append(sens.CL90()/eV) + self.ax.plot(ncavities_livetime/year, limits, color=kwargs["color"]) else: + years = [] standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year - self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + #self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([standard_exposure], [limits], s=40, marker="d", zorder=20, **kwargs) for ex in self.exposures: lt = ex/sens.EffectiveVolume() years.append(lt/year) sens.Experiment.livetime = lt - sigma_mbetas.append(sens.sensitivity()/eV**2) - self.ax.plot(self.exposures/m**3/year, sigma_mbetas, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) + #sigma_mbetas.append(sens.sensitivity()/eV**2) + limits.append(sens.CL90()/eV) + self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) @@ -809,14 +845,14 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): logger.info("Frequency: {:2e} Hz, Magnetic field: {:2e} T".format(freq/Hz, magnetic_field/T)) - # calcualte new cavity properties + # Calculate new cavity properties sens.CavityRadius() total_volumes.append(sens.CavityVolume()/m**3) effective_volumes.append(sens.EffectiveVolume()/m**3) sens.CavityPower() + #Optimization happens before calling this function, so I'm commenting out the below rho_limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - limits.append(np.min(rho_limits)) this_optimum_rho = self.rhos[np.argmin(rho_limits)] if this_optimum_rho == self.rhos[0] or this_optimum_rho == self.rhos[-1]: @@ -867,18 +903,23 @@ def range(self, start, stop): def save(self, savepath, **kwargs): logger.info("Saving") + legend_fontsize = self.fontsize - 2 + #handles, labels = self.fig.gca().get_legend_handles_labels() + #print(labels) + #order = [0,3,1,2] + #[handles[idx] for idx in order],[labels[idx] for idx in order], if self.density_axis: if self.track_length_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.85)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1.05,0.87), fontsize=legend_fontsize) #(0.15,0,1,0.85) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1,0.765)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1.,0.765), fontsize=legend_fontsize) elif self.frequency_axis: if self.magnetic_field_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 )) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 ), fontsize=legend_fontsize) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 )) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 ), fontsize=legend_fontsize) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.89,0.97)) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.88,0.955), fontsize=legend_fontsize) #(-0.,0,0.89,0.97) self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 28030a93..190d812f 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -195,39 +195,46 @@ class CavitySensitivity(Sensitivity): def __init__(self, config_path): Sensitivity.__init__(self, config_path) + ### #Initialization related to the effective volume: + ### self.Jprime_0 = 3.8317 - self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) self.cavity_freq = frequency(self.T_endpoint, self.MagneticField.nominal_field) self.CavityRadius() - self.CavityVolume() - self.CavityPower() - + #Get trap length from cavity length if not specified - if self.Experiment.trap_length == 0: + if not hasattr(self.Experiment, 'trap_length'): self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) + self.CavityVolume() + self.CavityPower() + #Calculate position dependent trapping efficiency self.pos_dependent_trapping_efficiency = trapping_efficiency( z_range = self.Experiment.trap_length /2, bg_magnetic_field = self.MagneticField.nominal_field, min_pitch_angle = self.FrequencyExtraction.minimum_angle_in_bandwidth, trap_flat_fraction = self.MagneticField.trap_flat_fraction ) - - #Detection efficiency: - #If use_threshold is either False or not included in the config file, use the inputted detection efficiency and RF background rate from the config file. - #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. - self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) + #We may decide to remove the "Threshold" section and move the threshold-related parameters to the "Efficiency" section. + if not self.Efficiency.usefixedvalue: + self.Threshold = NameSpace({opt: eval(self.cfg.get('Threshold', opt)) for opt in self.cfg.options('Threshold')}) + #Cyclotron radius is sometimes used in the effective volume calculation + self.cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) + + #Calculate the effective volume and print out related quantities self.EffectiveVolume() logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3), 2)) - logger.info("Total trap volume {} m^3".format(round(self.total_trap_volume/m**3), 2)) + logger.info("Total trap volume: {} m^3".format(round(self.total_trap_volume/m**3), 2)) logger.info("Cyclotron radius: {}m".format(self.cyc_rad/m)) if self.use_cyc_rad: logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") + #### #Initialization related to the energy resolution: + #### self.CRLB_constant = 12 #self.CRLB_constant = 90 if hasattr(self.FrequencyExtraction, "crlb_constant"): @@ -239,6 +246,9 @@ def __init__(self, config_path): if hasattr(self.FrequencyExtraction, "pitch_steps"): self.pitch_steps = self.FrequencyExtraction.pitch_steps logger.info("Using configured pitch_steps value") + + #Just calculated for comparison + self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used # CAVITY @@ -255,7 +265,7 @@ def CavityVolume(self): logger.info("Wavelength: {} cm".format(round(wavelength(self.T_endpoint, self.MagneticField.nominal_field)/cm, 3))) logger.info("Cavity radius: {} cm".format(round(self.cavity_radius/cm, 3))) logger.info("Cavity length: {} cm".format(round(2*self.cavity_radius*self.Experiment.cavity_L_over_D/cm, 3))) - logger.info("Total cavity volume {} m^3".format(round(self.total_cavity_volume/m**3)))\ + logger.info("Total cavity volume: {} m^3".format(round(self.total_cavity_volume/m**3, 3)))\ return self.total_cavity_volume @@ -270,20 +280,22 @@ def TrapVolume(self): def EffectiveVolume(self): self.total_trap_volume = self.TrapVolume() + if self.Efficiency.usefixedvalue: self.effective_volume = self.total_trap_volume * self.Efficiency.fixed_efficiency + self.use_cyc_rad = False else: #Detection efficiency if self.Threshold.use_detection_threshold: + #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. self.assign_background_rate_from_threshold() self.assign_detection_efficiency_from_threshold() else: + #If use_threshold is False, use the inputted detection efficiency and RF background rate from the config file. self.detection_efficiency = self.Efficiency.detection_efficiency self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV - #print("TEMP detection efficiency: {}".format(self.detection_efficiency)) #Radial efficiency - self.cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) if self.Efficiency.unusable_dist_from_wall >= self.cyc_rad: self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 self.use_cyc_rad = False @@ -438,8 +450,7 @@ def syst_frequency_extraction(self): endpoint_frequency = self.cavity_freq # using Pe and alpha (aka slope) from above - Pe = self.signal_power #/self.FrequencyExtraction.mode_coupling_efficiency - self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used + Pe = self.signal_power #/self.FrequencyExtraction.mode_coupling_efficiency self.slope = endpoint_frequency * 2 * np.pi * Pe/me/c0**2 # track slope self.time_window = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 03583adc..e0d569b9 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -38,7 +38,7 @@ "exposure_range": [1e-11, 1e4], "main_curve_upper_label": r"Phase IV scenario", #"Molecular, conservative", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.07\,\mathrm{eV}$", - "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, + "goals": {"Phase IV (0.04 eV)": 0.04}, "comparison_curve": False, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PhaseIII_325MHz_Experiment.cfg", "/termite/sensitivity_config_files/Config_atomic_325MHz_Experiment_conservative.cfg", @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Dec-30-2024.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Jan-7-2025.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -162,22 +162,23 @@ "track_length_axis": True, "cavity": True, "y_limits": [2e-2, 4], - "density_range": [1e14,3e18], + "density_range": [5e13,3e18], "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", - "goals": {"LFA (0.3 eV)": 0.3, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, "main_curve_color": "blue", - "comparison_curve_colors": ["red"], # "black"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase IV scenario: 150 MHz"], + "comparison_curve_colors": ["red", "black"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase IV scenario: 150 MHz", r"One Phase IV cavity"], "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 1.3e14, #0.0002 - "plot_key_parameters": True + "goals_x_position": 5.5e13, #0.0002 + "plot_key_parameters": True, + "goals_y_rel_position": 0.755 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -186,8 +187,8 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Oct-22-2024.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Jan-7-2025.pdf", "exposure_axis": True, # optional "figsize": (10,6), @@ -199,6 +200,7 @@ "exposure_axis": False, "density_axis": False, "livetime_axis": True, + #"ncavities_livetime_axis": True, "cavity": True, "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", @@ -206,22 +208,20 @@ "y_limits": [1.5e-2, 1], #"density_range": [1e12,1e19], "year_range": [0.1,10**3], - #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", - "main_curve_upper_label": r"Phase III scenario: 1 GHz, V = 0.25 m$^3$", - #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", - "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, + "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", + "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", - "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase III scenario: 560 MHz, V = 1.2 m$^3$", r"Phase IV scenario: 150 MHz, V = 74 m$^3 \times 10$"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$", "One Phase IV cavity"], "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, + "optimize_comparison_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.115, #2e12 #0.0002 - "goals_y_rel_position": 0.65, + "goals_x_position": 0.11, #2e12 #0.0002 + "goals_y_rel_position": 0.785, } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) @@ -253,7 +253,7 @@ #"main_curve_upper_label": r"LFA (atomic T)$\,-\,$1 GHz", "main_curve_upper_label": r"Phase III scenario: 1 GHz", #"main_curve_lower_label": r"$\sigma^\bar{B}_\mathrm{reco} = 0.16\,\mathrm{eV}$", - "goals": {"Phase IV (0.04 eV)": (0.04**2/1.64)}, + "goals": {"Phase IV (0.04 eV)": (0.04)}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], From 9b0b012d68b2ea297e34c71c900c68cdb011a219 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 8 Jan 2025 17:17:51 -0500 Subject: [PATCH 229/262] Legend position (bbox to anchor) can now be configured by user --- .../CavitySensitivityCurveProcessor.py | 13 ++++++++----- .../sensitivity/SensitivityCavityFormulas.py | 15 ++++++++++----- test_analysis/Cavity_Sensitivity_analysis.py | 10 ++++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index e6bc03eb..1456cda4 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -77,6 +77,7 @@ def InternalConfigure(self, params): # main plot configurations self.figsize = reader.read_param(params, 'figsize', (6,6)) self.legend_location = reader.read_param(params, 'legend_location', 'upper left') + self.legend_bbox_to_anchor = reader.read_param(params, 'legend_bbox_to_anchor', (0.15,0,1.1,0.87)) self.fontsize = reader.read_param(params, 'fontsize', 12) self.density_axis = reader.read_param(params, "density_axis", True) @@ -904,23 +905,25 @@ def range(self, start, stop): def save(self, savepath, **kwargs): logger.info("Saving") legend_fontsize = self.fontsize - 2 + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=self.legend_bbox_to_anchor, fontsize=legend_fontsize) #handles, labels = self.fig.gca().get_legend_handles_labels() #print(labels) #order = [0,3,1,2] #[handles[idx] for idx in order],[labels[idx] for idx in order], + """ if self.density_axis: if self.track_length_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1.05,0.87), fontsize=legend_fontsize) #(0.15,0,1,0.85) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1.1,0.87), fontsize=legend_fontsize) #(0.15,0,1,0.85) else: legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.15,0,1.,0.765), fontsize=legend_fontsize) elif self.frequency_axis: if self.magnetic_field_axis: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85 ), fontsize=legend_fontsize) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.85), fontsize=legend_fontsize) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95 ), fontsize=legend_fontsize) + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(0.14,0,1,0.95), fontsize=legend_fontsize) else: - legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.88,0.955), fontsize=legend_fontsize) #(-0.,0,0.89,0.97) - + legend=self.fig.legend(loc=self.legend_location, framealpha=0.95, bbox_to_anchor=(-0.,0,0.86,0.955), fontsize=legend_fontsize) #(-0.,0,0.88,0.955) #(-0.,0,0.89,0.97) + """ self.fig.tight_layout() #keywords = ", ".join(["%s=%s"%(key, value) for key, value in kwargs.items()]) metadata = {"Author": "p8/mermithid", diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 190d812f..1da58923 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -582,8 +582,10 @@ def syst_magnetic_field(self): return 0, 0 def det_efficiency_track_duration(self): - # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 - # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. + # Detection efficiency implemented based on René's slides: + # https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + # Also check the antenna paper for more details. + # Especially the section on the signal detection with matched filtering. mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.detection_threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) @@ -594,9 +596,12 @@ def assign_detection_efficiency_from_threshold(self): return self.detection_efficiency def rf_background_rate_cavity(self): - # Detection efficiency implemented based on René's slides https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 - # Also check the Antenna paper for more details. Especially the section on the signal detection with matched filtering. - # Assuming background rate constant of 1/(eV*s) for now. This constant will need to be determined from Monte Carlo simulations. + # Detection efficiency implemented based on René's slides + # https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + # Also check the antenna paper for more details, especially the section + # on the signal detection with matched filtering. + # Assuming background rate constant of 1/(eV*s) for now. This constant + # will need to be determined from Monte Carlo simulations. return chi2(df=2).sf(self.Threshold.detection_threshold)/(eV*s) def assign_background_rate_from_threshold(self): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index e0d569b9..400ed9fa 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,12 +150,13 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Jan-7-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Jan-8-2025.pdf", # optional "figsize": (7.0,6), "fontsize": 15, "track_length_axis": False, "legend_location": "upper left", + "legend_bbox_to_anchor": (0.17,-0.01,1.1,0.87), "molecular_axis": False, "atomic_axis": True, "density_axis": True, @@ -188,12 +189,13 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Jan-7-2025.pdf", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Jan-8-2025.pdf", "exposure_axis": True, # optional - "figsize": (10,6), + "figsize": (8.5, 6), #(10,6), "fontsize": 15, "legend_location": "upper right", + "legend_bbox_to_anchor": (-0.,0,0.86,0.955), "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, @@ -221,7 +223,7 @@ "upper_label_y_position": 0.7, "label_x_position": 0.115, #1.5e15, #0.02, #1e14, "goals_x_position": 0.11, #2e12 #0.0002 - "goals_y_rel_position": 0.785, + "goals_y_rel_position": 0.81, } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) From af2e0750f07bb7a0614107d21ad10f3e37805652 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 27 Jan 2025 12:21:40 -0500 Subject: [PATCH 230/262] Added ncav*eff*time plotting option; changed some straggler 1.64 factors to 1.28 in the conversation from sigma(mbeta^2) to a 90% CL --- .../CavitySensitivityCurveProcessor.py | 45 ++++++++++++------- .../sensitivity/SensitivityCavityFormulas.py | 16 ++++--- mermithid/sensitivity/SensitivityFormulas.py | 16 ++++--- test_analysis/Cavity_Sensitivity_analysis.py | 23 +++++----- 4 files changed, 61 insertions(+), 39 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 1456cda4..f5e0066a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -124,9 +124,9 @@ def InternalConfigure(self, params): logger.info("Plotting sensitivity vs. frequency") if self.optimize_main_density==False or self.optimize_comparison_density==False: logger.warning("You told me not to optimize the density, but I am doing so anyway! We need to fix this.") - elif self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis: + elif self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: self.add_sens_line = self.add_exposure_sens_line - logger.info("Plotting sensitivity vs. exposure or livetime") + logger.info("Plotting sensitivity vs. some exposure (e.g., livetime or ncavities*efficiency*livetime)") else: raise ValueError("No axis specified") @@ -184,7 +184,8 @@ def InternalConfigure(self, params): ncav_temp = self.sens_main.Experiment.n_cavities self.ncavities_livetime_range = [ncav_temp*self.year_range[0], ncav_temp*self.year_range[1]] - self.ncav_eff_time_range = [ncav_temp*self.year_range[0]*0.001, ncav_temp*self.year_range[1]*0.1] + self.ncav_eff_time_range = [ncav_temp*self.year_range[0]*0.01, ncav_temp*self.year_range[1]*0.1] + self.ncav_eff_times = np.logspace(np.log10(self.ncav_eff_time_range[0]), np.log10(self.ncav_eff_time_range[1]), 100)*year return True @@ -195,8 +196,11 @@ def InternalRun(self): self.sens_main.print_systematics() self.sens_with_configured_density = self.sens_main.CL90() logger.info("Sensitivity before density optimization: {} eV".format(self.sens_with_configured_density/eV)) - logger.info("Number density: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) - logger.info("Corresponding track length: {} s".format(self.sens_main.track_length(self.sens_main.Experiment.number_density)/s)) + logger.info("Number density before optimization: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) + logger.info("Corresponding track length before density optimization: {} s".format(self.sens_main.track_length(self.sens_main.Experiment.number_density)/s)) + logger.info("***Efficiencies before density optimization:***") + self.sens_main.print_Efficiencies() + logger.info("***Done printing pre-optimization***") # create main plot self.create_plot() @@ -300,7 +304,7 @@ def InternalRun(self): if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) - logger.info('T2 in Veff: {}'.format(rho*self.sens_main.effective_volume)) + logger.info('Tritium in Veff: {}'.format(rho*self.sens_main.effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info('Total signal: {}'.format(rho*self.sens_main.effective_volume* @@ -373,7 +377,7 @@ def InternalRun(self): if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {}'.format(limit2)) - logger.info('T2 in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) + logger.info('Tritium in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) logger.info('Total signal: {}'.format(rho2*self.sens_ref[i].effective_volume* @@ -669,7 +673,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" if sens.Experiment.atomic: #label="Atomic, reaching pilot-T target".format(n_cavities, livetime_for_label) - label="1 Phase IV cavity, 1 year of livetime".format(n_cavities, livetime_for_label) + label="{} Phase IV cavity, {} year of livetime".format(n_cavities, livetime_for_label) else: label="Molecular, {} cavity, {} year".format(n_cavities, livetime_for_label) @@ -701,11 +705,11 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" logger.info("Livetime and mass limit for single point: {}, {}".format(sens.Experiment.livetime/year, limit)) elif self.ncavities_livetime_axis: ncav_time = sens.Experiment.n_cavities*sens.Experiment.livetime/year - self.ax.scatter([ncav_time], [limit], marker="s", s=25, color=color, label=label, zorder=10) + self.ax.scatter([ncav_time], [limit], marker="d", s=25, color='k', label=label, zorder=10) logger.info("Ncavities*livetime and mass limit for single point: {}, {}".format(ncav_time, limit)) elif self.ncav_eff_time_axis: - ncav_eff_time = sens.effective_volume/sens.total_trap_volume*sens.Experiment.livetime/year - self.ax.scatter([ncav_eff_time], [limit], marker="s", s=25, color=color, label=label, zorder=10) + ncav_eff_time = sens.EffectiveVolume()/sens.TrapVolume()*sens.Experiment.n_cavities*sens.Experiment.livetime/year + self.ax.scatter([ncav_eff_time], [limit], marker="d", s=25, color='k', label=label, zorder=10) logger.info("Ncavities*efficiency*livetime and mass limit for single point: {}, {}".format(ncav_eff_time, limit)) sens.print_statistics() @@ -734,13 +738,24 @@ def add_exposure_sens_line(self, sens, exposure_type=None, plot_key_params=False limits.append(sens.CL90()/eV) self.ax.plot(self.years/year, limits, color=kwargs["color"]) elif exposure_type=="ncavities_livetime": + self.ax.scatter([sens.Experiment.n_cavities*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) ncavities_livetime = [] - for lt in self.years: + year_list = self.years/sens.Experiment.n_cavities + for lt in year_list: sens.Experiment.livetime = lt - ncavities_livetime.append(self.Experiment.n_cavities*lt) - #sigma_mbetas.append(sens.sensitivity()/eV**2) + ncavities_livetime.append(sens.Experiment.n_cavities*lt/year) + limits.append(sens.CL90()/eV) + self.ax.plot(ncavities_livetime, limits, color=kwargs["color"]) + elif exposure_type=="ncav_eff_time": + ncav_eff = sens.EffectiveVolume()/sens.TrapVolume()*sens.Experiment.n_cavities + self.ax.scatter([ncav_eff*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) + ncav_eff_time = [] + year_list = self.ncav_eff_times/ncav_eff + for lt in year_list: + sens.Experiment.livetime = lt + ncav_eff_time.append(ncav_eff*lt/year) limits.append(sens.CL90()/eV) - self.ax.plot(ncavities_livetime/year, limits, color=kwargs["color"]) + self.ax.plot(ncav_eff_time, limits, color=kwargs["color"]) else: years = [] standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 1da58923..2117b26b 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -224,10 +224,17 @@ def __init__(self, config_path): #Cyclotron radius is sometimes used in the effective volume calculation self.cyc_rad = cyclotron_radius(self.cavity_freq, self.T_endpoint) + #Assigning the background constant if it's not in the config file + if hasattr(self.Experiment, "bkgd_constant"): + self.bkgd_constant = self.Experiment.bkgd_constant + else: + self.bkgd_constant = 1 + logger.info("Using background rate constant of 1/eV/s") + #Calculate the effective volume and print out related quantities self.EffectiveVolume() logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3), 2)) - logger.info("Total trap volume: {} m^3".format(round(self.total_trap_volume/m**3), 2)) + logger.info("Total trap volume: {} m^3".format(round(self.total_trap_volume/m**3), 3)) logger.info("Cyclotron radius: {}m".format(self.cyc_rad/m)) if self.use_cyc_rad: logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") @@ -241,7 +248,7 @@ def __init__(self, config_path): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") - #Numbr of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation + #Number of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation self.pitch_steps = 100 if hasattr(self.FrequencyExtraction, "pitch_steps"): self.pitch_steps = self.FrequencyExtraction.pitch_steps @@ -600,9 +607,8 @@ def rf_background_rate_cavity(self): # https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 # Also check the antenna paper for more details, especially the section # on the signal detection with matched filtering. - # Assuming background rate constant of 1/(eV*s) for now. This constant - # will need to be determined from Monte Carlo simulations. - return chi2(df=2).sf(self.Threshold.detection_threshold)/(eV*s) + # The background constant will need to be determined from Monte Carlo simulations. + return chi2(df=2).sf(self.Threshold.detection_threshold)*self.bkgd_constant/(eV*s) def assign_background_rate_from_threshold(self): self.RF_background_rate_per_eV = self.rf_background_rate_cavity() diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index f52cfcc7..690cda3a 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -124,7 +124,8 @@ def DeltaEWidth(self): + 8*np.log(2)*(np.sum(sigmas**2))) def StatSens(self): - """Pure statistic sensitivity assuming Poisson count experiment in a single bin""" + """Pure statistic sensitivity assuming Poisson count experiment in a single bin + As defined, it needs to be squared before being added to the systematic component""" sig_rate = self.SignalRate() DeltaE = self.DeltaEWidth() sens = 2/(3*sig_rate*self.Experiment.LiveTime)*np.sqrt(sig_rate*self.Experiment.LiveTime*DeltaE @@ -132,7 +133,8 @@ def StatSens(self): return sens def SystSens(self): - """Pure systematic componenet to sensitivity""" + """Pure systematic component to sensitivity + As defined, it needs to be squared before being added to the statistical component""" labels, sigmas, deltas = self.get_systematics() sens = 4*np.sqrt(np.sum((sigmas*deltas)**2)) return sens @@ -158,10 +160,10 @@ def CL90(self, **kwargs): """ Gives 90% CL upper limit on neutrino mass.""" # 90% of gaussian are contained in +-1.64 sigma region #return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - return np.sqrt(1.64*self.sensitivity(**kwargs)) + return np.sqrt(1.28*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(1.64*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + return np.sqrt(1.28*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # PHYSICS Functions @@ -230,7 +232,7 @@ def get_systematics(self): def print_statistics(self): print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.StatSens())/meV), "meV") + print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() @@ -247,9 +249,9 @@ def print_systematics(self): #except AttributeError: # pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") - print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.SystSens())/meV), "meV") + print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") logger.info("Carrier frequency uncertainty: {} Hz".format(np.sqrt(self.var_f_c_CRLB)/Hz)) - return np.sqrt(1.64*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV + return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 400ed9fa..361d19d1 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_linked-bkgd-and-eff_Jan-8-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_KPPs_Jan-10-2025.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -166,7 +166,7 @@ "density_range": [5e13,3e18], "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, - "comparison_curve": True, + "comparison_curve": False, "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], @@ -189,7 +189,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_Jan-8-2025.pdf", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_ncav-eff-time_curve_Jan-8-2025.pdf", "exposure_axis": True, # optional "figsize": (8.5, 6), #(10,6), @@ -201,29 +201,28 @@ "atomic_axis": False, "exposure_axis": False, "density_axis": False, - "livetime_axis": True, - #"ncavities_livetime_axis": True, + "ncav_eff_time_axis": True, "cavity": True, "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "add_1year_1cav_point_to_last_ref": False, + "add_1year_1cav_point_to_last_ref": True, "y_limits": [1.5e-2, 1], #"density_range": [1e12,1e19], "year_range": [0.1,10**3], "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$", "One Phase IV cavity"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", + "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$"], #"One Phase IV cavity", "main_curve_color": "blue", - "comparison_curve_colors": ["red", "black"], + "comparison_curve_colors": ["red"], #"black", "optimize_main_density": False, "optimize_comparison_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, - "label_x_position": 0.115, #1.5e15, #0.02, #1e14, - "goals_x_position": 0.11, #2e12 #0.0002 - "goals_y_rel_position": 0.81, + "label_x_position": 0.115, + "goals_x_position": 1.13e-3, #0.11, <-- Number for ncavities*livetime + "goals_y_rel_position": 0.84, #0.81, <-- Number for ncavities*livetime } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) From ee94c38e04e1f7d0ea473b65808b41a348b20c96 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 27 Jan 2025 12:38:06 -0500 Subject: [PATCH 231/262] Changed back to 1.64 (I got confused about the direction of the change) --- mermithid/sensitivity/SensitivityFormulas.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 690cda3a..8cc176df 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -160,10 +160,10 @@ def CL90(self, **kwargs): """ Gives 90% CL upper limit on neutrino mass.""" # 90% of gaussian are contained in +-1.64 sigma region #return np.sqrt(np.sqrt(1.64)*self.sensitivity(**kwargs)) - return np.sqrt(1.28*self.sensitivity(**kwargs)) + return np.sqrt(1.64*self.sensitivity(**kwargs)) def sterial_m2_limit(self, Ue4_sq): - return np.sqrt(1.28*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) + return np.sqrt(1.64*np.sqrt((self.StatSens()/Ue4_sq)**2 + self.SystSens()**2)) # PHYSICS Functions @@ -232,7 +232,7 @@ def get_systematics(self): def print_statistics(self): print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.StatSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.StatSens())/meV), "meV") - print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.StatSens())/meV), "meV") + print("Statistical mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.StatSens())/meV), "meV") def print_systematics(self): labels, sigmas, deltas = self.get_systematics() @@ -249,9 +249,9 @@ def print_systematics(self): #except AttributeError: # pass print("Contribution to sigma_(m_beta^2)", " "*18, "%.2f"%(self.SystSens()/meV**2), "meV^2 ->", "%.2f"%(np.sqrt(self.SystSens())/meV), "meV") - print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.28*self.SystSens())/meV), "meV") + print("Systematic mass limit", " "*18, "%.2f"%(np.sqrt(1.64*self.SystSens())/meV), "meV") logger.info("Carrier frequency uncertainty: {} Hz".format(np.sqrt(self.var_f_c_CRLB)/Hz)) - return np.sqrt(1.28*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV + return np.sqrt(1.64*self.SystSens())/meV, np.sqrt(np.sum(sigmas**2))/meV def syst_doppler_broadening(self): # estimated standard deviation of Doppler broadening distribution from From 3806bb732edb1ad039e8cf5aea0d7f104a2ccc73 Mon Sep 17 00:00:00 2001 From: mohiuddinrazu Date: Mon, 3 Feb 2025 22:38:34 -0500 Subject: [PATCH 232/262] =?UTF-8?q?added=20Gauss=E2=80=93Laguerre=20quadra?= =?UTF-8?q?ture=20integration=20method=20for=20speed=20and=20stability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sensitivity/SensitivityCavityFormulas.py | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 2117b26b..c7b67ff9 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -9,7 +9,7 @@ ''' import numpy as np from scipy.stats import ncx2, chi2 -from scipy.integrate import quad +from scipy.special import roots_laguerre from mermithid.misc.Constants_numericalunits import * from mermithid.misc.CRESFunctions_numericalunits import * @@ -589,14 +589,42 @@ def syst_magnetic_field(self): return 0, 0 def det_efficiency_track_duration(self): - # Detection efficiency implemented based on René's slides: - # https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 - # Also check the antenna paper for more details. - # Especially the section on the signal detection with matched filtering. + """ + Detection efficiency implemented based on René's slides, with faster and stable implementation using Gauss-Laguerre quadrature: + https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 + Gauss-Laguerre Quadrature: https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + + Parameters + ---------- + None + + Returns + ------- + detection_efficiency : float + SNR and threshold dependent detection efficieny. + + Notes + ----- + Also check the antenna paper for more details. + Especially the section on the signal detection with matched filtering. + """ + # Calculate the mean track duration mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) - result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.detection_threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) - return result, abs_err + + # Roots and weights for the Laguerre polynomial + x, w = roots_laguerre(250) #n=250 is the number of quadrature points + + # Scale the track duration to match the form of Gauss-Laguerre quadrature + scaled_x = x * mean_track_duration + + # Evaluate the non-central chi-squared dist values at the scaled quadrature points + sf_values = ncx2(df=2, nc=scaled_x / tau_snr_ex_carrier).sf(self.Threshold.detection_threshold) + #plt.plot(sf_values) + + # Calculate and return the integration result from weighted sum + result = np.sum(w * sf_values) + return result def assign_detection_efficiency_from_threshold(self): self.detection_efficiency, self.abs_err = self.det_efficiency_track_duration() From 801a1517f402f5299766c9117c362ab0c4b6630b Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 4 Feb 2025 15:44:57 -0800 Subject: [PATCH 233/262] Added function to calculate trap_length from cavity L/D, assumes 80% of cavity length. Automatically called for sensitivity scans. If they aren't scanning trap length --- .../Sensitivity/SensitivityParameterScanProcessor.py | 11 ++++++++++- mermithid/sensitivity/SensitivityCavityFormulas.py | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index edb519db..6c3bfd22 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -13,6 +13,7 @@ import matplotlib.pyplot as plt import numpy as np import os +import concurrent.futures @@ -182,12 +183,20 @@ def InternalRun(self): except KeyError as e: logger.error(f"Parameter {param} not found in {category}") raise e - + self.sens_main.__dict__[category].__dict__[param] = parameter_value read_back = self.sens_main.__dict__[category].__dict__[param] #setattr(self.sens_main, self.scan_parameter_name, parameter_value) #read_back = getattr(self.sens_main, self.scan_parameter_name) logger.info(f"Setting {self.scan_parameter_name} to {parameter_value/self.scan_parameter_unit} and reading back: {read_back/ self.scan_parameter_unit}") + + # pitch angle set equal + if(param == "min_pitch_used_in_analysis"): + self.sens_main.__dict__["FrequencyExtraction"].__dict__["minimum_angle_in_bandwidth"] = parameter_value + + # If the scanned param isn't trap length, calc trap length for cavity L/D + if (param != "trap_length"): + self.sens_main.TrapLength() logger.info("Calculating cavity experiment radius, volume, effective volume, power") self.sens_main.CavityRadius() diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 2117b26b..778716a3 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -204,7 +204,8 @@ def __init__(self, config_path): #Get trap length from cavity length if not specified if not hasattr(self.Experiment, 'trap_length'): - self.Experiment.trap_length = 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + self.Experiment.trap_length = 0.8 * 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + logger.info("Calc'd trap length: {} m".format(round(self.Experiment.trap_length/m, 3), 2)) self.Efficiency = NameSpace({opt: eval(self.cfg.get('Efficiency', opt)) for opt in self.cfg.options('Efficiency')}) self.CavityVolume() @@ -329,6 +330,10 @@ def BoxTrappingEfficiency(self): self.box_trapping_efficiency = np.cos(self.FrequencyExtraction.minimum_angle_in_bandwidth) return self.box_trapping_efficiency + def TrapLength(self): + self.Experiment.trap_length = 0.8 * 2 * self.cavity_radius * self.Experiment.cavity_L_over_D + logger.info("Calc'd trap length: {} m".format(round(self.Experiment.trap_length/m, 3), 2)) + def CavityPower(self): # from Hamish's atomic calculator #Jprime_0 = 3.8317 From 7345d356c05eaf093878df95655f85ed589f3ab7 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 9 Feb 2025 20:17:58 -0500 Subject: [PATCH 234/262] Fixed det eff calculation to have factor of 2 multiplying SNR; calculating det eff using total power (not just carrier); optimized detection threshold in most parts of code; updated CRLB calculation (closer to current knowledge but still needs soem work) --- .../CavitySensitivityCurveProcessor.py | 175 +++++++++++++----- .../sensitivity/SensitivityCavityFormulas.py | 64 +++---- test_analysis/Cavity_Sensitivity_analysis.py | 6 +- 3 files changed, 160 insertions(+), 85 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index f5e0066a..e8f4f9d6 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -95,6 +95,7 @@ def InternalConfigure(self, params): self.year_range = reader.read_param(params, "year_range", [0.1, 20]) self.exposure_range = reader.read_param(params, "exposure_range", [1e-10, 1e3]) self.frequency_range = reader.read_param(params, "frequency_range", [1e6, 1e9]) + self.det_thresh_range = reader.read_param(params, 'det_thresh_range', [15, 85]) self.ylim = reader.read_param(params, 'y_limits', [1e-2, 1e2]) @@ -115,7 +116,7 @@ def InternalConfigure(self, params): # key parameter plots self.make_key_parameter_plots = reader.read_param(params, 'plot_key_parameters', False) - + if self.density_axis: self.add_sens_line = self.add_density_sens_line logger.info("Plotting sensitivity vs. density") @@ -177,11 +178,12 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 50)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 40)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz - + self.thresholds = np.linspace(self.det_thresh_range[0], self.det_thresh_range[1], 10) + ncav_temp = self.sens_main.Experiment.n_cavities self.ncavities_livetime_range = [ncav_temp*self.year_range[0], ncav_temp*self.year_range[1]] self.ncav_eff_time_range = [ncav_temp*self.year_range[0]*0.01, ncav_temp*self.year_range[1]*0.1] @@ -192,13 +194,31 @@ def InternalConfigure(self, params): def InternalRun(self): - logger.info("Systematics before density optimization:") + + #Calculating the sensitivity for parameters in the main config file + self.sens_with_configured_density_and_thresh = self.sens_main.CL90() + + #Optimizing the detection threshold for the main config file + #Before the density optimization + self.track_duration_before_opt = self.sens_main.track_length(self.sens_main.Experiment.number_density) + tau_snr_before_opt = self.sens_main.calculate_tau_snr(self.track_duration_before_opt, self.sens_main.FrequencyExtraction.carrier_power_fraction) + SNR_track_duration = self.track_duration_before_opt/tau_snr_before_opt + #threshs_before_opt = np.linspace(0.5*SNR_track_duration, 1.0*SNR_track_duration, 10) + thresh_limits = [self.sens_main.CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] + index = np.argmin(thresh_limits) + self.thresh_opt_before_n_opt = self.thresholds[index] + self.sens_with_configured_density_and_opt_thresh = thresh_limits[index] + self.sens_main.Threshold.detection_threshold = self.thresh_opt_before_n_opt + + logger.info("Sensitivity before density or threshold optimization: {} eV".format(self.sens_with_configured_density_and_thresh/eV)) + logger.info("Sensitivity with optimized threshold, before density opt: {} eV".format(self.sens_with_configured_density_and_opt_thresh/eV)) + logger.info("Optimum threshold: {}".format(self.thresh_opt_before_n_opt)) + logger.info("Scanned thresholds: {}".format(self.thresholds)) + logger.info("Systematics before density optimization and after threshold optimization:") self.sens_main.print_systematics() - self.sens_with_configured_density = self.sens_main.CL90() - logger.info("Sensitivity before density optimization: {} eV".format(self.sens_with_configured_density/eV)) logger.info("Number density before optimization: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) - logger.info("Corresponding track length before density optimization: {} s".format(self.sens_main.track_length(self.sens_main.Experiment.number_density)/s)) - logger.info("***Efficiencies before density optimization:***") + logger.info("Corresponding track length before density optimization: {} s".format(self.track_duration_before_opt/s)) + logger.info("***Efficiencies before density optimization, after threshold opt:***") self.sens_main.print_Efficiencies() logger.info("***Done printing pre-optimization***") @@ -223,14 +243,34 @@ def InternalRun(self): self.add_goal(value, key) if self.density_axis and self.add_point_at_configured_density: - self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density/eV], marker="s", s=25, color='b', label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) + self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color='b', label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) # optimize density if self.optimize_main_density: - limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - opt = np.argmin(limit) + thresh_opt_main = [] + limits = [] + for rho in self.rhos: + track_duration = self.sens_main.track_length(rho) #, self.sens_main.T_endpoint, molecular=(not self.Experiment.atomic) + tau_snr_ex_carrier = self.sens_main.calculate_tau_snr(track_duration, self.sens_main.FrequencyExtraction.carrier_power_fraction) + SNR_track_duration = track_duration/tau_snr_ex_carrier + thresh_limits = [] + for thresh in self.thresholds: + self.sens_main.Threshold.detection_threshold = thresh + thresh_limits.append(self.sens_main.CL90(Experiment={"number_density": rho})) + index = np.argmin(thresh_limits) + thresh_opt_main.append(self.thresholds[index]) + limits.append(thresh_limits[index]) + #limit = [self.sens_main.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + opt = np.argmin(limits) rho_opt = self.rhos[opt] self.sens_main.Experiment.number_density = rho_opt + self.sens_main.Threshold.detection_threshold = thresh_opt_main[opt] + logger.info("Optimized thresholds at each density: {}".format(thresh_opt_main)) + logger.info("Optimized threshold at optimum n: {}".format(thresh_opt_main[opt])) + + else: + #Use the optimized detection threshold even if the density isn't optimized + self.sens_main.Threshold.detection_threshold = self.thresh_opt_before_n_opt # if B is list plot line for each B @@ -270,7 +310,10 @@ def InternalRun(self): self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label, zorder=2) # PRINT OPTIMUM RESULTS - + + self.sens_main.Experiment.number_density = rho_opt + self.sens_main.Threshold.detection_threshold = thresh_opt_main[opt] + # if the magnetic field uncertainties were configured above, set them back to the first value in the list if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): self.sens_main.MagneticField.sigmae_r = self.sigmae_theta_r[0] * eV @@ -291,6 +334,7 @@ def InternalRun(self): logger.info("Total bandwidth: {} MHz".format(self.sens_main.required_bw/MHz)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_main.larmor_power/W, self.sens_main.signal_power/W)) logger.info('Hanneke / Larmor power = {}'.format(self.sens_main.signal_power/self.sens_main.larmor_power)) + logger.info("eta = slope*track_length/(2*omega_c) = {}".format(self.sens_main.eta)) if self.sens_main.FrequencyExtraction.crlb_on_sidebands: logger.info("Trap p: {}".format(self.sens_main.p)) @@ -303,7 +347,7 @@ def InternalRun(self): if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") - logger.info('CL90 limit: {}'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) + logger.info('CL90 limit: {} eV'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) logger.info('Tritium in Veff: {}'.format(rho*self.sens_main.effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) @@ -319,44 +363,60 @@ def InternalRun(self): self.sens_main.print_systematics() if self.make_key_parameter_plots: - # First key parameter plot: Stat and Syst vs. density - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] - for n in self.rhos: - temp_rho = deepcopy(self.sens_main.Experiment.number_density) - self.sens_main.Experiment.number_density = n - labels, sigmas, deltas = self.sens_main.get_systematics() - sigma_startf.append(sigmas[1]) - stat_on_mbeta2.append(self.sens_main.StatSens()) - syst_on_mbeta2.append(self.sens_main.SystSens()) - self.sens_main.Experiment.number_density = temp_rho - - sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) - fig = plt.figure() - plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') - plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") - plt.legend() - plt.tight_layout() - plt.savefig("stat_and_syst_vs_density.pdf") - - fig = plt.figure() - plt.loglog(self.rhos*m**3, sigma_startf/eV) - plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") - plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") - plt.tight_layout() - plt.savefig("resolution_from_CRLB_vs_density.pdf") + if not self.optimize_main_density: + logger.info("Set optimize_main_density to True to make key parameter plots") + else: + # First key parameter plot: Stat and Syst vs. density + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + for i in range(len(self.rhos)): + temp_rho = deepcopy(self.sens_main.Experiment.number_density) + self.sens_main.Experiment.number_density = self.rhos[i] + self.sens_main.Threshold.detection_threshold = thresh_opt_main[i] + labels, sigmas, deltas = self.sens_main.get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_main.StatSens()) + syst_on_mbeta2.append(self.sens_main.SystSens()) + self.sens_main.Experiment.number_density = temp_rho + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig("stat_and_syst_vs_density.pdf") + + fig = plt.figure() + plt.loglog(self.rhos*m**3, sigma_startf/eV) + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") + plt.tight_layout() + plt.savefig("resolution_from_CRLB_vs_density.pdf") # Optimize comparison curves over density if self.comparison_curve: for i in range(len(self.sens_ref)): if self.optimize_comparison_density: - limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] - limit2 = np.argmin(limit_ref) - rho_opt_ref = self.rhos[limit2] - self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + thresh_opt_comparison = [] + limits_ref = [] + for rho in self.rhos: + thresh_limits = [] + for thresh in self.thresholds: + self.sens_ref[i].Threshold.detection_threshold = thresh + thresh_limits.append(self.sens_ref[i].CL90(Experiment={"number_density": rho})) + index = np.argmin(thresh_limits) + thresh_opt_comparison.append(self.thresholds[index]) + limits_ref.append(thresh_limits[index]) + #limit_ref = [self.sens_ref[i].CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] + limit2_index = np.argmin(limits_ref) + rho_opt_ref = self.rhos[limit2_index] + limit2 = limits_ref[limit2_index] #self.sens_ref[i].CL90(Experiment={"number_density": rho_opt_ref}) + #logger.info("Optimized thresholds at each density: {}".format(thresh_opt_comparison)) else: - limit2 = self.sens_ref[i].CL90()/eV + limit_ref = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] + limit2 = np.min(limit_ref) #self.sens_ref[i].CL90()/eV if self.optimize_comparison_density: logger.info('COMPARISON CURVE at optimum density:') @@ -376,7 +436,7 @@ def InternalRun(self): self.sens_ref[i].print_Efficiencies() if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") - logger.info('CL90 limit: {}'.format(limit2)) + logger.info('CL90 limit: {}'.format(limit2/eV)) logger.info('Tritium in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) @@ -642,8 +702,19 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): #det_effs = [] temp_rho = deepcopy(sens.Experiment.number_density) + thresh_opt = [] for rho in self.rhos: - limits.append(sens.CL90(Experiment={"number_density": rho})/eV) + track_duration = sens.track_length(rho) #, self.sens_main.T_endpoint, molecular=(not self.Experiment.atomic) + tau_snr_ex_carrier = sens.calculate_tau_snr(track_duration, sens.FrequencyExtraction.carrier_power_fraction) + SNR_track_duration = track_duration/tau_snr_ex_carrier + thresh_limits = [] + #thresholds = np.linspace(10, 100, 10) #0.3*SNR_track_duration, 0.8*SNR_track_duration, 10) + for thresh in self.thresholds: + sens.Threshold.detection_threshold = thresh + thresh_limits.append(sens.CL90(Experiment={"number_density": rho})/eV) + index = np.argmin(thresh_limits) + thresh_opt.append(self.thresholds[index]) + limits.append(thresh_limits[index]) #(sens.CL90(Experiment={"number_density": rho})/eV) resolutions.append(sens.sigma_K_noise/meV) crlb_window.append(sens.best_time_window/ms) crlb_max_window.append(sens.time_window/ms) @@ -653,7 +724,7 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): #print(det_effs) sens.Experiment.number_density = temp_rho self.ax.plot(self.rhos*m**3, limits, **kwargs) - logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) + #logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) if self.make_key_parameter_plots and plot_key_params: self.kp_ax[0].plot(self.rhos*m**3, resolutions, **kwargs) @@ -695,7 +766,7 @@ def add_single_exposure_point(self, sens, livetime=1*year, n_cavities=1, color=" standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year #*exposure_fraction #lt = standard_exposure/sens.EffectiveVolume() #sens.Experiment.livetime = lt - limit = sens.CL90()/eV + limit = sens.CL90()/eV #DETECTION THRESHOLD MAY NOT BE OPTIMIZED - NEED TO CHECK if self.exposure_axis: self.ax.scatter([standard_exposure], [limit], marker="s", s=25, color=color, label=label, zorder=10) @@ -867,7 +938,9 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): effective_volumes.append(sens.EffectiveVolume()/m**3) sens.CavityPower() - #Optimization happens before calling this function, so I'm commenting out the below + # Optimization happens before calling this function, so I'm commenting out the below + # Nevermind. But the optimization should be optional, as with other variables on the x-axis. + # Note that the detection threshold is not optimized here. rho_limits = [sens.CL90(Experiment={"number_density": rho})/eV for rho in self.rhos] limits.append(np.min(rho_limits)) this_optimum_rho = self.rhos[np.argmin(rho_limits)] @@ -891,7 +964,7 @@ def add_frequency_sens_line(self, sens, plot_key_params=True, **kwargs): self.ax2.plot(self.frequencies/Hz, limits, **kwargs) - logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) + #logger.info('Minimum limit at {}: {}'.format(self.rhos[np.argmin(limits)]*m**3, np.min(limits))) # change cavity back to config file """sens.MagneticField.nominal_field = configured_magnetic_field diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 2117b26b..9816a1e4 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -78,7 +78,7 @@ def mean_field_frequency_variation(cyclotron_frequency, length_diameter_ratio, m # Because of the different electron trajectories in the trap, # An electron will see a slightly different magnetic field # depending on its position in the trap, especially the pitch angle. - # This is a rough estimate of the mean field variation, inspired by calcualtion performed by Rene. + # This is a rough estimate of the mean field variation, inspired by calculation performed by Rene. #y = (90-max_pitch_angle)/4 phi_rad = (np.pi/2-max_pitch_angle) return q*phi_rad**2*cyclotron_frequency*(10/length_diameter_ratio) @@ -242,8 +242,8 @@ def __init__(self, config_path): #### #Initialization related to the energy resolution: #### - self.CRLB_constant = 12 - #self.CRLB_constant = 90 + #No longer using this CRLB_constant. If this change sticks, will remove it. + self.CRLB_constant = 6 if hasattr(self.FrequencyExtraction, "crlb_constant"): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") @@ -445,6 +445,22 @@ def print_SNRs(self, rho_opt): logger.info("Opimtum energy window: {} eV".format(self.DeltaEWidth()/eV)) """ + def frequency_variance_from_CRLB(self, tau_SNR): + self.eta = self.slope*self.time_window/(4*self.cavity_freq*np.pi) + if self.eta < 1e-6: + # This is for the case where the track is flat (almost no slope), and where we + # treat it as a pure sinusoid (don't fit the slope when extracting the frequency). + # Applies for a complex signal. + return self.FrequencyExtraction.CRLB_scaling_factor*(6*tau_SNR/self.time_window**3)/(2*np.pi)**2 + else: + # Non-zero, fitted slope. Still assumes that alpha*T/2 << omega_c. + # The first term relies on the relation delta_t_start = sqrt(20)*tau_snr. This is from Equation 6.40 of Nick's thesis, + # derived in Appendix A and verified with an MC study. + # Using a factor of 23 instead of 20, from re-calculating Nick's integrals (though this derivation is approximate). + # Nick's derivation uses an expression for P_fa - we need to check if it's consistent with what Rene uses now. + return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + 96*tau_SNR/self.time_window**3)/(2*np.pi)**2 + + def syst_frequency_extraction(self): # cite{https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398} (Section 1.2, p 7-9) # Are we double counting the antenna collection efficiency? We use it here. Does it also impact the effective volume, v_eff ? @@ -467,25 +483,8 @@ def syst_frequency_extraction(self): tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.carrier_power_fraction) tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero, self.FrequencyExtraction.carrier_power_fraction) - - # use different crlb based on slope - # delta_E_slope = abs(kin_energy(endpoint_frequency, self.MagneticField.nominal_field)-kin_energy(endpoint_frequency+self.slope*ms, self.MagneticField.nominal_field)) - # logger.info("slope is {} Hz/ms".format(self.slope/Hz*ms)) - # logger.info("slope corresponds to {} meV / ms".format(delta_E_slope/meV)) - # if True: #self.time_window_slope_zero >= self.time_window: - # logger.info("slope is approximately 0: {} meV".format(delta_E_slope/meV)) - self.var_f_c_CRLB = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2 - self.best_time_window = self.time_window - - # non constant slope - self.var_f_CRLB_slope_fitted = self.FrequencyExtraction.CRLB_scaling_factor*(20*(self.slope*tau_snr_full_length)**2 + self.CRLB_constant*tau_snr_full_length/self.time_window**3)/(2*np.pi)**2 - if self.CRLB_constant > 10: self.var_f_c_CRLB = self.var_f_CRLB_slope_fitted - - """ - # sigma_f from Cramer-Rao lower bound in eV - self.sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*sigma_f_CRLB*c0**2 - # delta_sigma_K_f_CRLB = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*delta_sigma_f_CRLB*c0**2 - """ + #Calculate the frequency variance from the CRLB + self.var_f_c_CRLB = self.frequency_variance_from_CRLB(self, tau_snr_full_length) # sigma_f from pitch angle reconstruction if self.FrequencyExtraction.crlb_on_sidebands: @@ -494,8 +493,9 @@ def syst_frequency_extraction(self): tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) # (sigmaf_lsb)^2: - var_f_sideband_crlb = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2 - + var_f_sideband_crlb = self.frequency_variance_from_CRLB(self, tau_snr_full_length_sideband) + #var_f_sideband_crlb = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2 + m = self.FrequencyExtraction.sideband_order #For convenience #Define phi_max, corresponding to the minimum pitch angle @@ -594,12 +594,14 @@ def det_efficiency_track_duration(self): # Also check the antenna paper for more details. # Especially the section on the signal detection with matched filtering. mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) - result, abs_err = quad(lambda track_duration: ncx2(df=2, nc=track_duration/tau_snr_ex_carrier).sf(self.Threshold.detection_threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf) - return result, abs_err + # Using "total power," assumed here to be carrier power + one sideband's power. + # This is for a representative pitch angle---not the same as the power of a 90° pitch electron's carrier. + tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction + self.FrequencyExtraction.sideband_power_fraction) + result = quad(lambda track_duration: ncx2(df=2, nc=2*track_duration/tau_snr_ex_total).sf(self.Threshold.detection_threshold)*1/mean_track_duration*np.exp(-track_duration/mean_track_duration), 0, np.inf)[0] + return result def assign_detection_efficiency_from_threshold(self): - self.detection_efficiency, self.abs_err = self.det_efficiency_track_duration() + self.detection_efficiency = self.det_efficiency_track_duration() return self.detection_efficiency def rf_background_rate_cavity(self): @@ -659,8 +661,8 @@ def print_SNRs(self, rho=None): logger.info("Optimum energy window: {} eV".format(self.DeltaEWidth()/eV)) - logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(np.sqrt(self.var_f_CRLB_slope_fitted)/Hz)) - logger.info("CRLB constant: {}".format(self.CRLB_constant)) + #logger.info("CRLB if slope is nonzero and needs to be fitted: {} Hz".format(np.sqrt(self.var_f_CRLB_slope_fitted)/Hz)) + #logger.info("CRLB constant: {}".format(self.CRLB_constant)) logger.info("**Done printing SNR parameters.**") return self.noise_temp, SNR_1eV_90deg, track_duration @@ -672,7 +674,7 @@ def print_Efficiencies(self): # radial and detection efficiency are configured in the config file logger.info("Radial efficiency: {}".format(self.radial_efficiency)) logger.info("Detection efficiency: {}".format(self.detection_efficiency)) - logger.info("Detection efficiency integration error: {}".format(self.abs_err)) + #logger.info("Detection efficiency integration error: {}".format(self.abs_err)) logger.info("Trapping efficiency: {}".format(self.pos_dependent_trapping_efficiency)) logger.info("Efficiency from axial frequency cut: {}".format(self.fa_cut_efficiency)) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 361d19d1..1183df0a 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_KPPs_Jan-10-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_Feb-9-2025.pdf", # optional "figsize": (7.0,6), "fontsize": 15, @@ -165,7 +165,7 @@ "y_limits": [2e-2, 4], "density_range": [5e13,3e18], "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", - "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "goals": {"LFA (0.65 eV)": 0.65, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": False, "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], @@ -210,7 +210,7 @@ #"density_range": [1e12,1e19], "year_range": [0.1,10**3], "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", - "goals": {"LFA (0.4 eV)": 0.4, "Phase IV (0.04 eV)": 0.04}, + "goals": {"LFA (0.65 eV)": 0.65, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$"], #"One Phase IV cavity", From f5ef79ae6430b93c911f9226c3beed471780922e Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 9 Feb 2025 20:21:25 -0500 Subject: [PATCH 235/262] Fixed usage of new function to calculate CRLB variance --- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 36af97b2..4f7e6d94 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -489,7 +489,7 @@ def syst_frequency_extraction(self): tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero, self.FrequencyExtraction.carrier_power_fraction) #Calculate the frequency variance from the CRLB - self.var_f_c_CRLB = self.frequency_variance_from_CRLB(self, tau_snr_full_length) + self.var_f_c_CRLB = self.frequency_variance_from_CRLB(tau_snr_full_length) # sigma_f from pitch angle reconstruction if self.FrequencyExtraction.crlb_on_sidebands: @@ -498,7 +498,7 @@ def syst_frequency_extraction(self): tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) # (sigmaf_lsb)^2: - var_f_sideband_crlb = self.frequency_variance_from_CRLB(self, tau_snr_full_length_sideband) + var_f_sideband_crlb = self.frequency_variance_from_CRLB(tau_snr_full_length_sideband) #var_f_sideband_crlb = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2 m = self.FrequencyExtraction.sideband_order #For convenience From ca328ca96fa0490cac215a28a3f430b342f85144 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 11 Feb 2025 10:47:25 -0500 Subject: [PATCH 236/262] Calculating det eff for power vs radius then averaging over radii; fixed code to print efficiencies at optimum density; removed events that hit wall in power averaging and detection eff; printing about bkgd constant and use of threshold --- mermithid/cavity/HannekeFunctions.py | 1 - .../CavitySensitivityCurveProcessor.py | 21 ++++---- .../sensitivity/SensitivityCavityFormulas.py | 53 +++++++++++++------ test_analysis/Cavity_Sensitivity_analysis.py | 14 ++--- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/mermithid/cavity/HannekeFunctions.py b/mermithid/cavity/HannekeFunctions.py index 983c1f92..be185df4 100644 --- a/mermithid/cavity/HannekeFunctions.py +++ b/mermithid/cavity/HannekeFunctions.py @@ -1,4 +1,3 @@ - import numpy as np from scipy import special from numericalunits import T, kB, hbar, e, me, eV, c0, eps0, m, Hz, MHz diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 5104f36d..260619fb 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -3,7 +3,7 @@ Author: C. Claessens, T. E. Weiss Date: 06/07/2023 -Updated: January 2025 +Updated: February 2025 ''' from __future__ import absolute_import @@ -178,11 +178,11 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 100)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 50)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz - self.thresholds = np.linspace(self.det_thresh_range[0], self.det_thresh_range[1], 25) + self.thresholds = np.linspace(self.det_thresh_range[0], self.det_thresh_range[1], 40) ncav_temp = self.sens_main.Experiment.n_cavities self.ncavities_livetime_range = [ncav_temp*self.year_range[0], ncav_temp*self.year_range[1]] @@ -308,6 +308,7 @@ def InternalRun(self): self.sens_main.Experiment.number_density = rho_opt self.sens_main.Threshold.detection_threshold = thresh_opt_main[opt_index] + self.sens_main.EffectiveVolume() # if the magnetic field uncertainties were configured above, set them back to the first value in the list if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): @@ -338,7 +339,7 @@ def InternalRun(self): logger.info("Uncertainty from determination of f_carrier and f_lsb, due to noise: {} eV".format(self.sens_main.sigma_K_noise/eV)) self.sens_main.print_SNRs(rho) - self.sens_main.print_Efficiencies() + self.sens_main.print_Efficiencies() if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") @@ -417,9 +418,13 @@ def InternalRun(self): if self.optimize_comparison_density: logger.info('COMPARISON CURVE at optimum density:') rho2 = rho_opt_ref + self.sens_ref[i].Experiment.number_density = rho2 + self.sens_ref[i].Threshold.detection_threshold = thresh_opt_comparison[limit2_index] + self.sens_main.EffectiveVolume() else: logger.info('COMPARISON CURVE at configured density (not optimum):') rho2 = self.sens_main.Experiment.number_density + logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho2*(m**3))) logger.info("Loaded Q: {}".format(self.sens_ref[i].loaded_q)) logger.info('Larmor power = {} W, Hanneke power = {} W'.format(self.sens_ref[i].larmor_power/W, self.sens_ref[i].signal_power/W)) @@ -432,7 +437,7 @@ def InternalRun(self): self.sens_ref[i].print_Efficiencies() if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") - logger.info('CL90 limit: {}'.format(limit2/eV)) + logger.info('CL90 limit: {} eV'.format(limit2/eV)) logger.info('Tritium in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) @@ -468,8 +473,8 @@ def print_disclaimers(self): logger.info("2. Trap design is not yet linked to cavity L/D in the sensitivity model. So, \ the model does *not* capture how reducing L/D worsens the resolution.") logger.info("3. In reality, the frequency resolution could be worse or somewhat better \ - than predicted by the general CRLB calculation used here. See work by Florian.") - logger.info("4. The analytic sensitivity formula oaccounts for energy resolution contributions \ + than predicted by the chirp CRLB calculation used here. See work by Florian.") + logger.info("4. The analytic sensitivity formula accounts for energy resolution contributions \ that are *normally distributed*. (Energy resolution = std of the response fn \ that broadens the spectrum.) To account for asymmetric contributions, generate \ spectra with MC sampling and then analyze them. This can be done in mermithid.") @@ -713,8 +718,6 @@ def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): crlb_max_window.append(sens.time_window/ms) crlb_slope_zero_window.append(sens.time_window_slope_zero/ms) #det_effs.append(self.sens_main.detection_efficiency) - logger.info("Optimized thresholds at each density: {}".format(thresh_opt)) - logger.info("Optimized threshold at optimum n: {}".format(thresh_opt[index])) #print(det_effs) sens.Experiment.number_density = temp_rho diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index bd90cafd..b3bea2b3 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -228,6 +228,7 @@ def __init__(self, config_path): #Assigning the background constant if it's not in the config file if hasattr(self.Experiment, "bkgd_constant"): self.bkgd_constant = self.Experiment.bkgd_constant + logger.info("Using background rate constant of {}/eV/s".format(self.bkgd_constant)) else: self.bkgd_constant = 1 logger.info("Using background rate constant of 1/eV/s") @@ -258,6 +259,11 @@ def __init__(self, config_path): #Just calculated for comparison self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used + if self.Threshold.use_detection_threshold: + logger.info("Overriding any detection eff and RF background in the config file; calculating these from the detection_threshold.") + else: + logger.info("Using the detection eff and RF background rate from the config file.") + # CAVITY def CavityRadius(self): @@ -295,14 +301,16 @@ def EffectiveVolume(self): else: #Detection efficiency if self.Threshold.use_detection_threshold: - #If the_threshold is True, calculate the detection efficiency given the SNR of data and the threshold. + #Calculating the detection efficiency given the SNR of data and the threshold. + #If the config file contains a detection effciency or RF background rate, they are overridden. self.assign_background_rate_from_threshold() self.assign_detection_efficiency_from_threshold() else: - #If use_threshold is False, use the inputted detection efficiency and RF background rate from the config file. + #Using the inputted detection efficiency and RF background rate from the config file. self.detection_efficiency = self.Efficiency.detection_efficiency self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV + #Radial efficiency if self.Efficiency.unusable_dist_from_wall >= self.cyc_rad: self.radial_efficiency = (self.cavity_radius - self.Efficiency.unusable_dist_from_wall)**2/self.cavity_radius**2 @@ -335,20 +343,23 @@ def TrapLength(self): logger.info("Calc'd trap length: {} m".format(round(self.Experiment.trap_length/m, 3), 2)) def CavityPower(self): - # from Hamish's atomic calculator #Jprime_0 = 3.8317 max_ax_freq, mean_field, z_t = axial_motion(self.MagneticField.nominal_field, self.FrequencyExtraction.minimum_angle_in_bandwidth, self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth, - self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) + self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) #1000 - #self.signal_power = self.FrequencyExtraction.mode_coupling_efficiency * self.CavityLoadedQ() * self.FrequencyExtraction.hanneke_factor * self.T_endpoint/eV * e/C * Jprime_0**2 / (2*np.pi**2*self.Experiment.cavity_L_over_D*2*self.cavity_radius**3/m**3 * frequency(self.T_endpoint, self.MagneticField.nominal_field)*s)*W - self.signal_power = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=2000), + #The np.random.triangluar function weights the radii, accounting for the fact that there are more electrons at large radii than small ones + power_vs_r_with_zeros = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=50), z_t, self.CavityLoadedQ(), 2*self.Experiment.cavity_L_over_D*self.cavity_radius, self.cavity_radius, - self.cavity_freq)) + self.cavity_freq), axis=1) + #Remove zeros, since these represent electrons that hit the cavity wall and are not detected + self.signal_power_vs_r = power_vs_r_with_zeros[power_vs_r_with_zeros != 0] + + self.signal_power = np.mean(self.signal_power_vs_r) return self.signal_power @@ -385,7 +396,7 @@ def CavityLoadedQ(self): # SYSTEMATICS # Generic systematics are implemented in the parent class in SensitivityFormulas.py - def calculate_tau_snr(self, time_window, power_fraction=1): + def calculate_tau_snr(self, time_window, power_fraction=1, tau_snr_array_for_radii=False): """ power_fraction may be used as a carrier or a sideband power fraction, relative to the power of a 90 degree carrier. @@ -419,7 +430,10 @@ def calculate_tau_snr(self, time_window, power_fraction=1): # Pe = rad_power(self.T_endpoint, self.FrequencyExtraction.pitch_angle, self.MagneticField.nominal_field) # logger.info("Power: {}".format(Pe/W)) - Pe = self.signal_power * power_fraction + if tau_snr_array_for_radii: + Pe = self.signal_power_vs_r * power_fraction + else: + Pe = self.signal_power * power_fraction P_signal_received = Pe*db_to_pwr_ratio(att_cir_db_freq+att_line_db_freq) self.received_power = P_signal_received @@ -610,8 +624,11 @@ def det_efficiency_track_duration(self): Especially the section on the signal detection with matched filtering. """ # Calculate the mean track duration + #FIX: Only do the lines below ones for a given density; don't repeat for each threshold being scanned ... mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction + self.FrequencyExtraction.sideband_power_fraction) + tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction + self.FrequencyExtraction.sideband_power_fraction, tau_snr_array_for_radii=self.Efficiency.calculate_det_eff_for_sampled_radii) + if isinstance(tau_snr_ex_total, float): + tau_snr_ex_total = [tau_snr_ex_total] # Roots and weights for the Laguerre polynomial x, w = roots_laguerre(100) #n=100 is the number of quadrature points @@ -620,12 +637,14 @@ def det_efficiency_track_duration(self): scaled_x = x * mean_track_duration # Evaluate the non-central chi-squared dist values at the scaled quadrature points - sf_values = ncx2(df=2, nc=2*scaled_x / tau_snr_ex_total).sf(self.Threshold.detection_threshold) - #plt.plot(sf_values) + sf_values = np.array([ncx2(df=2, nc=2 * scaled_x / tau_snr).sf(self.Threshold.detection_threshold) for tau_snr in tau_snr_ex_total]) # Calculate and return the integration result from weighted sum - result = np.sum(w * sf_values) - return result + eff_for_each_r = np.sum(w * sf_values, axis=1) + + #Average efficiencies over the sampled electron radii. Weighting for radial distribution is accounted for in sampling, earlier. + avg_efficiency = np.mean(eff_for_each_r) + return avg_efficiency def assign_detection_efficiency_from_threshold(self): self.detection_efficiency = self.det_efficiency_track_duration() @@ -697,6 +716,9 @@ def print_SNRs(self, rho=None): def print_Efficiencies(self): + logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) + logger.info("Total efficiency: {}".format(self.effective_volume/self.total_trap_volume)) + if not self.Efficiency.usefixedvalue: # radial and detection efficiency are configured in the config file logger.info("Radial efficiency: {}".format(self.radial_efficiency)) @@ -705,9 +727,6 @@ def print_Efficiencies(self): logger.info("Trapping efficiency: {}".format(self.pos_dependent_trapping_efficiency)) logger.info("Efficiency from axial frequency cut: {}".format(self.fa_cut_efficiency)) logger.info("SRI factor: {}".format(self.Experiment.sri_factor)) - - logger.info("Effective volume: {} mm^3".format(round(self.effective_volume/mm**3, 3))) - logger.info("Total efficiency: {}".format(self.effective_volume/self.total_trap_volume)) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 44e738d3..110d6c60 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,21 +150,21 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_Feb-10-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_test_Feb-10-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, "track_length_axis": False, "legend_location": "upper left", - "legend_bbox_to_anchor": (0.17,-0.01,1.1,0.87), + "legend_bbox_to_anchor": (0.155,-0.01,1.12,0.885), #(0.17,-0.01,1.1,0.87), "molecular_axis": False, "atomic_axis": True, "density_axis": True, "track_length_axis": True, - "cavity": True, + "effs_for_sampled_radii": True, "y_limits": [2e-2, 4], - "density_range": [5e14,3e18], #5e13 - "det_thresh_range": [15, 135], + "density_range": [3e14,3e18], #5e13 + "det_thresh_range": [5, 115], "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", "goals": {"LFA (0.5 eV)": 0.5, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, @@ -178,9 +178,9 @@ "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 5.5e13, #0.0002 + "goals_x_position": 3.3e14, #5.5e13, "plot_key_parameters": True, - "goals_y_rel_position": 0.755 + "goals_y_rel_position": 0.78 #0.755 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) From f2037526ca1324bca9264233748c4ccc5ad28f0c Mon Sep 17 00:00:00 2001 From: Razu Mohiuddin Date: Tue, 18 Feb 2025 11:29:33 -0500 Subject: [PATCH 237/262] Gauss-Laguerre method calculations added --- .../sensitivity/SensitivityCavityFormulas.py | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index c7b67ff9..da3d9785 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -590,9 +590,35 @@ def syst_magnetic_field(self): def det_efficiency_track_duration(self): """ - Detection efficiency implemented based on René's slides, with faster and stable implementation using Gauss-Laguerre quadrature: + Detection efficiency implemented based on René's slides, with faster and stable implementation using Gauss-Laguerre quadrature (G-L method): https://3.basecamp.com/3700981/buckets/3107037/documents/8013439062 Gauss-Laguerre Quadrature: https://en.wikipedia.org/wiki/Gauss%E2%80%93Laguerre_quadrature + + + The following changes were made to the original integral to fit the G-L method: + + Original integrand: ∫[0 to \inf] ncx2(df=2, nc=t/τ).sf(thres) * (1/μ) * exp(-t/μ) dt + + Where, + t = track_duration + μ (\mu) = mean_track_duration + τ (\tau) = tau_snr_ex_carrier + thres = detection_threshold + + We do the change of variable, x = t / μ + so, t = x μ + or, dt = μ dx + + Substituting into the original integral: + + ∫[0 to \inf] ncx2(df=2, nc=xμ/τ).sf(thres) * (1/μ) * exp(-x) μ dx + + The μ's cancel out, and the integral takes the form: + + ∫[0 to \inf] f(x) * exp(-x) dx + + where, f(x) = ncx2(df=2, nc=xμ/τ).sf(thres) + Parameters ---------- @@ -613,10 +639,10 @@ def det_efficiency_track_duration(self): tau_snr_ex_carrier = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction) # Roots and weights for the Laguerre polynomial - x, w = roots_laguerre(250) #n=250 is the number of quadrature points + x, w = roots_laguerre(100) #n=100 is the number of quadrature points # Scale the track duration to match the form of Gauss-Laguerre quadrature - scaled_x = x * mean_track_duration + scaled_x = x * mean_track_duration # scaled_x = xμ # Evaluate the non-central chi-squared dist values at the scaled quadrature points sf_values = ncx2(df=2, nc=scaled_x / tau_snr_ex_carrier).sf(self.Threshold.detection_threshold) From eb4f1e61ee32b130ca1c1508e74c4b305268ffe8 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Tue, 18 Feb 2025 12:36:06 -0800 Subject: [PATCH 238/262] Added a line to increase sample count of radii if calculate_det_eff_for_sampled_radii = False. Doesn't impact run time while greatly reducing fluctuations in parameter scans. --- mermithid/sensitivity/SensitivityCavityFormulas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index b3bea2b3..be1c48ca 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -351,7 +351,9 @@ def CavityPower(self): self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction, trajectory = 1000) #1000 #The np.random.triangluar function weights the radii, accounting for the fact that there are more electrons at large radii than small ones - power_vs_r_with_zeros = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=50), + r_sample_size = 50 + if(not self.Efficiency.calculate_det_eff_for_sampled_radii): r_sample_size = 1000 + power_vs_r_with_zeros = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=r_sample_size), z_t, self.CavityLoadedQ(), 2*self.Experiment.cavity_L_over_D*self.cavity_radius, self.cavity_radius, From f643ddae42585e48403ec369e045ccbf948a32f2 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Thu, 20 Feb 2025 09:40:27 -0800 Subject: [PATCH 239/262] Added log x scale plotting of densities if the parameter value array is logarithmic --- .../Sensitivity/SensitivityParameterScanProcessor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 6c3bfd22..10f37705 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -304,7 +304,9 @@ def InternalRun(self): plt.ylabel(r"90% CL $m_\beta$ (eV)", fontsize=self.fontsize) if self.plot_sensitivity_scan_on_log_scale: plt.yscale("log") - + # TODO log x here + if(self.scan_parameter_scale == "log"): + plt.xscale("log") for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) plt.axhline(value, label=key, color="grey", linestyle="--") From a2688c3ad50c80df02723e6bfeeedf22b2528ee6 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sat, 1 Mar 2025 23:16:41 -0800 Subject: [PATCH 240/262] Recalculate bkgd rate for optimum density so that correct background is printed --- .../processors/Sensitivity/CavitySensitivityCurveProcessor.py | 4 ++++ test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 260619fb..d3e611a3 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -345,6 +345,8 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {} eV'.format(self.sens_main.CL90(Experiment={"number_density": rho})/eV)) logger.info('Tritium in Veff: {}'.format(rho*self.sens_main.effective_volume)) + self.sens_main.assign_background_rate_from_threshold() + self.sens_main.BackgroundRate() logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info('Total signal: {}'.format(rho*self.sens_main.effective_volume* @@ -439,6 +441,8 @@ def InternalRun(self): logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {} eV'.format(limit2/eV)) logger.info('Tritium in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) + self.sens_ref[i].assign_background_rate_from_threshold() + self.sens_ref[i].BackgroundRate() logger.info('RF background: {}/eV/s'.format(self.sens_ref[i].RF_background_rate_per_eV*eV*s)) logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) logger.info('Total signal: {}'.format(rho2*self.sens_ref[i].effective_volume* diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 110d6c60..8f2777e7 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -150,7 +150,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_test_Feb-10-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_Feb-11-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, From 9df77acc75336d2644fd987eb78a578fe7a44260 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Sun, 2 Mar 2025 13:39:05 -0800 Subject: [PATCH 241/262] Updated threshold LFA sensitivity vs. density plot and numbers --- test_analysis/Cavity_Sensitivity_analysis.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 8f2777e7..d56715b3 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -146,11 +146,10 @@ # Configuration for Sensitivity vs. density plot -# Currently comparing conservative atomic vs. scenario that reaches target without statistics boost sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_Feb-11-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_March-2-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -190,7 +189,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_ncav-eff-time_curve_Jan-8-2025.pdf", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_March-2-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.5, 6), #(10,6), @@ -202,7 +201,8 @@ "atomic_axis": False, "exposure_axis": False, "density_axis": False, - "ncav_eff_time_axis": True, + "ncav_eff_time_axis": False, + "livetime_axis": True, "cavity": True, "add_PhaseII": True, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", @@ -222,8 +222,8 @@ "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, - "goals_x_position": 1.13e-3, #0.11, <-- Number for ncavities*livetime - "goals_y_rel_position": 0.84, #0.81, <-- Number for ncavities*livetime + "goals_x_position": 1.13e-1, #1.13e-3, <-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + "goals_y_rel_position": 0.84, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) From 3f2808d9e3effa25b065ec32a67bcc3079681f42 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Sun, 2 Mar 2025 21:18:44 -0800 Subject: [PATCH 242/262] Added condition to set r_sample=1000 if config has fixed efficiency True --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index be1c48ca..dcf7ccad 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -352,7 +352,7 @@ def CavityPower(self): #The np.random.triangluar function weights the radii, accounting for the fact that there are more electrons at large radii than small ones r_sample_size = 50 - if(not self.Efficiency.calculate_det_eff_for_sampled_radii): r_sample_size = 1000 + if((not self.Efficiency.calculate_det_eff_for_sampled_radii) or (self.Efficiency.usefixedvalue)): r_sample_size = 1000 power_vs_r_with_zeros = np.mean(larmor_orbit_averaged_hanneke_power(np.random.triangular(0, self.cavity_radius, self.cavity_radius, size=r_sample_size), z_t, self.CavityLoadedQ(), 2*self.Experiment.cavity_L_over_D*self.cavity_radius, From 2deb553904463b7bf884b28b7b2d44d0465759b3 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Mon, 3 Mar 2025 09:47:47 -0800 Subject: [PATCH 243/262] Fixed issue where sigmae_phi was set to take from the theta input, while the phi input was ignored --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index dcf7ccad..eeae25d7 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -604,7 +604,7 @@ def syst_magnetic_field(self): sigmaE_meanB = self.BToKeErr(sigma_meanB*B, B) sigmaE_r = self.MagneticField.sigmae_r sigmaE_theta = self.MagneticField.sigmae_theta - sigmaE_phi = self.MagneticField.sigmae_theta + sigmaE_phi = self.MagneticField.sigmae_phi sigma = np.sqrt(sigmaE_meanB**2 + sigmaE_r**2 + sigmaE_theta**2 + sigmaE_phi**2) return sigma, frac_uncertainty*sigma else: From b824e0c79b8f6ddb95b154c83b704704b2b8569a Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 3 Mar 2025 15:38:32 -0500 Subject: [PATCH 244/262] Fixed problem where sideband order wasn't affecting Q --- .../CavitySensitivityCurveProcessor.py | 17 +++++++---- .../sensitivity/SensitivityCavityFormulas.py | 7 +++-- test_analysis/Cavity_Sensitivity_analysis.py | 30 +++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index d3e611a3..9b33ad6b 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -178,7 +178,7 @@ def InternalConfigure(self, params): logger.warn("No experiment is configured to be atomic") # densities, exposures, runtimes - self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 50)/m**3 + self.rhos = np.logspace(np.log10(self.density_range[0]), np.log10(self.density_range[1]), 80)/m**3 self.exposures = np.logspace(np.log10(self.exposure_range[0]), np.log10(self.exposure_range[1]), 100)*m**3*year self.years = np.logspace(np.log10(self.year_range[0]), np.log10(self.year_range[1]), 100)*year self.frequencies = np.logspace(np.log10(self.frequency_range[0]), np.log10(self.frequency_range[1]), 20)*Hz @@ -216,8 +216,12 @@ def InternalRun(self): self.sens_main.print_systematics() logger.info("Number density before optimization: {} \m^3".format(self.sens_main.Experiment.number_density*m**3)) logger.info("Corresponding track length before density optimization: {} s".format(self.track_duration_before_opt/s)) - logger.info("***Efficiencies before density optimization, after threshold opt:***") + logger.info("***Efficiencies and bkgd before density optimization, after threshold opt:***") self.sens_main.print_Efficiencies() + self.sens_main.assign_background_rate_from_threshold() + self.sens_main.BackgroundRate() + logger.info('RF background: {}/eV/s'.format(self.sens_main.RF_background_rate_per_eV*eV*s)) + logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info("***Done printing pre-optimization***") # create main plot @@ -306,9 +310,12 @@ def InternalRun(self): # PRINT OPTIMUM RESULTS - self.sens_main.Experiment.number_density = rho_opt - self.sens_main.Threshold.detection_threshold = thresh_opt_main[opt_index] - self.sens_main.EffectiveVolume() + if self.optimize_main_density: + #This is done above - should confirm whether it actually needs to be done again, or not + self.sens_main.Experiment.number_density = rho_opt + self.sens_main.Threshold.detection_threshold = thresh_opt_main[opt_index] + self.sens_main.EffectiveVolume() + #MAY NEED TO DO THE ABOVE FOR THE SET DENSITY IN THE CONFIG FILE, WHEN NOT OPTIMIZING MAIN DENSITY # if the magnetic field uncertainties were configured above, set them back to the first value in the list if self.configure_sigma_theta_r and (isinstance(self.sigmae_theta_r, list) or isinstance(self.sigmae_theta_r, np.ndarray)): diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index be1c48ca..63e498a0 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -259,8 +259,9 @@ def __init__(self, config_path): #Just calculated for comparison self.larmor_power = rad_power(self.T_endpoint, np.pi/2, self.MagneticField.nominal_field) # currently not used - if self.Threshold.use_detection_threshold: - logger.info("Overriding any detection eff and RF background in the config file; calculating these from the detection_threshold.") + if not self.Efficiency.usefixedvalue: + if self.Threshold.use_detection_threshold: + logger.info("Overriding any detection eff and RF background in the config file; calculating these from the detection_threshold.") else: logger.info("Using the detection eff and RF background rate from the config file.") @@ -381,7 +382,7 @@ def CavityLoadedQ(self): self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) - required_bw_axialfrequency = max_ax_freq + required_bw_axialfrequency = max_ax_freq*self.FrequencyExtraction.sideband_order self.required_bw_axialfrequency = required_bw_axialfrequency required_bw_meanfield = required_bw_meanfield = np.abs(frequency(self.T_endpoint, mean_field) - endpoint_frequency) required_bw = np.add(required_bw_axialfrequency,required_bw_meanfield) # Broadcasting diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index d56715b3..f4651b14 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_March-2-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_March-3-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -165,7 +165,7 @@ "density_range": [3e14,3e18], #5e13 "det_thresh_range": [5, 115], "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", - "goals": {"LFA (0.5 eV)": 0.5, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], @@ -177,9 +177,9 @@ "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 3.3e14, #5.5e13, + "goals_x_position": 6e14, #3.3e14, #5.5e13, "plot_key_parameters": True, - "goals_y_rel_position": 0.78 #0.755 + "goals_y_rel_position": 0.79 #0.755 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -189,10 +189,10 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_March-2-2025.pdf", #ncav-eff-time + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_March-3-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional - "figsize": (8.5, 6), #(10,6), + "figsize": (8.3, 6.3), #(10,6), "fontsize": 15, "legend_location": "upper right", "legend_bbox_to_anchor": (-0.,0,0.86,0.955), @@ -204,26 +204,26 @@ "ncav_eff_time_axis": False, "livetime_axis": True, "cavity": True, - "add_PhaseII": True, + "add_PhaseII": False, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "add_1year_1cav_point_to_last_ref": True, - "y_limits": [1.5e-2, 1], + "add_1year_1cav_point_to_last_ref": False, + "y_limits": [2.5e-2, 1], #"density_range": [1e12,1e19], "year_range": [0.1,10**3], "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", - "goals": {"LFA (0.65 eV)": 0.65, "Phase IV (0.04 eV)": 0.04}, + "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", - "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$"], #"One Phase IV cavity", + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$", "One Phase IV cavity"], "main_curve_color": "blue", - "comparison_curve_colors": ["red"], #"black", + "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, "optimize_comparison_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, - "goals_x_position": 1.13e-1, #1.13e-3, <-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime - "goals_y_rel_position": 0.84, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime + "goals_x_position": 1.0, #1.13e-3, <-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + "goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } #sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") #sens_curve.Configure(sens_config_dict) From e7eb34efc57fd6bb5609f90c3a7bf5afb4c165a1 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 3 Mar 2025 16:08:06 -0500 Subject: [PATCH 245/262] Fixed problem of using sense_main in two comparison-curve-specific places --- .../processors/Sensitivity/CavitySensitivityCurveProcessor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 9b33ad6b..42d756ff 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -429,10 +429,10 @@ def InternalRun(self): rho2 = rho_opt_ref self.sens_ref[i].Experiment.number_density = rho2 self.sens_ref[i].Threshold.detection_threshold = thresh_opt_comparison[limit2_index] - self.sens_main.EffectiveVolume() + self.sens_ref[i].EffectiveVolume() else: logger.info('COMPARISON CURVE at configured density (not optimum):') - rho2 = self.sens_main.Experiment.number_density + rho2 = self.sens_ref[i].Experiment.number_density logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho2*(m**3))) logger.info("Loaded Q: {}".format(self.sens_ref[i].loaded_q)) From a9cc5b441617eebbf6cc588e01df8af2f85e6f91 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Mon, 3 Mar 2025 21:53:48 -0500 Subject: [PATCH 246/262] Handling density/threshold optimization options better for comparison curves --- .../CavitySensitivityCurveProcessor.py | 47 ++++++++++--------- test_analysis/Cavity_Sensitivity_analysis.py | 27 ++++++----- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 42d756ff..323d7f17 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -422,7 +422,9 @@ def InternalRun(self): logger.info("Optimized threshold at optimum n: {}".format(thresh_opt_comparison[limit2_index])) else: limit_ref = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] - limit2 = np.min(limit_ref) #self.sens_ref[i].CL90()/eV + index2 = np.argmin(thresh_limits) + self.sens_ref[i].Threshold.detection_threshold = self.thresholds[index2] + limit2 = limit_ref[index2] if self.optimize_comparison_density: logger.info('COMPARISON CURVE at optimum density:') @@ -433,6 +435,7 @@ def InternalRun(self): else: logger.info('COMPARISON CURVE at configured density (not optimum):') rho2 = self.sens_ref[i].Experiment.number_density + logger.info("Optimized threshold at configured density: {}".format(self.thresholds[index2])) logger.info('veff = {} m**3, rho = {} /m**3:'.format(self.sens_ref[i].effective_volume/(m**3), rho2*(m**3))) logger.info("Loaded Q: {}".format(self.sens_ref[i].loaded_q)) @@ -444,8 +447,8 @@ def InternalRun(self): self.sens_ref[i].print_SNRs(rho2) self.sens_ref[i].print_Efficiencies() - if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: - logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") + #if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: + # logger.info("NUMBERS BELOW ARE FOR THE HIGHEST-EXPOSURE POINT ON THE CURVE:") logger.info('CL90 limit: {} eV'.format(limit2/eV)) logger.info('Tritium in Veff: {}'.format(rho2*self.sens_ref[i].effective_volume)) self.sens_ref[i].assign_background_rate_from_threshold() @@ -463,7 +466,25 @@ def InternalRun(self): self.sens_ref[i].print_statistics() self.sens_ref[i].print_systematics() - self.add_comparison_curve(label=self.comparison_curve_label) + #Add comparison curves + label=self.comparison_curve_label + if self.livetime_axis: + limits = self.add_sens_line(self.sens_ref[i], exposure_type="livetime", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + elif self.ncavities_livetime_axis: + limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncavities_livetime", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + elif self.ncav_eff_time_axis: + limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncav_eff_time", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + else: + limits = self.add_sens_line(self.sens_ref[i], plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i], zorder=5) + #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) + + if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: + if self.add_1year_1cav_point_to_last_ref: + logger.info("Going to add single exposure point") + self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) + + + #self.add_comparison_curve(label=self.comparison_curve_label) #self.add_arrow(self.sens_main) # save plot @@ -653,23 +674,7 @@ def add_magnetic_field_axis(self): gamma = self.sens_main.T_endpoint/(me*c0**2) + 1 ax3.set_xlim(self.frequencies[0]/(e/(2*np.pi*me)/gamma)/T, self.frequencies[-1]/(e/(2*np.pi*me)/gamma)/T) - def add_comparison_curve(self, label, color='k'): - - for a in range(len(self.sens_ref)): - if self.livetime_axis: - limits = self.add_sens_line(self.sens_ref[a], exposure_type="livetime", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) - elif self.ncavities_livetime_axis: - limits = self.add_sens_line(self.sens_ref[a], exposure_type="ncavities_livetime", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) - elif self.ncav_eff_time_axis: - limits = self.add_sens_line(self.sens_ref[a], exposure_type="ncav_eff_time", plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a]) - else: - limits = self.add_sens_line(self.sens_ref[a], plot_key_params=False, color=self.comparison_curve_colors[a], label=label[a], zorder=5) - #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) - - if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: - if self.add_1year_1cav_point_to_last_ref: - logger.info("Going to add single exposure point") - self.add_single_exposure_point(self.sens_ref[-1], color=self.comparison_curve_colors[-1]) + #def add_comparison_curve(self, label, color='k'): def add_arrow(self, sens): #I am not sure why the two line below are here diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index f4651b14..cf9203d9 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -181,15 +181,15 @@ "plot_key_parameters": True, "goals_y_rel_position": 0.79 #0.755 } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_March-3-2025.pdf", #ncav-eff-time + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_ncav-eff-time_curve_target_March-3-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -201,20 +201,21 @@ "atomic_axis": False, "exposure_axis": False, "density_axis": False, - "ncav_eff_time_axis": False, - "livetime_axis": True, + "ncav_eff_time_axis": True, + "ncavities_livetime_axis": False, + "livetime_axis": False, "cavity": True, "add_PhaseII": False, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "add_1year_1cav_point_to_last_ref": False, + "add_1year_1cav_point_to_last_ref": True, "y_limits": [2.5e-2, 1], #"density_range": [1e12,1e19], "year_range": [0.1,10**3], "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$", "One Phase IV cavity"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$"], #, "One Phase IV cavity"], "main_curve_color": "blue", "comparison_curve_colors": ["red", "black"], "optimize_main_density": False, @@ -222,12 +223,12 @@ "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, - "goals_x_position": 1.0, #1.13e-3, <-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + "goals_x_position": 4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime "goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() From 692772063e00da93bf7eaf479ab6fdea8bc60b54 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 20 Mar 2025 07:54:55 -0700 Subject: [PATCH 247/262] Fixed event rate calcs to use *2 for T2 and *1 for T; added operating density points for comparison curves; improved plot formatting and text --- .../CavitySensitivityCurveProcessor.py | 47 ++++++++-- .../sensitivity/SensitivityCavityFormulas.py | 2 +- mermithid/sensitivity/SensitivityFormulas.py | 2 +- test_analysis/Cavity_Sensitivity_analysis.py | 91 ++++++++++++++----- 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 323d7f17..3a5bb38e 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -167,6 +167,17 @@ def InternalConfigure(self, params): #logger.info("Comparison curve is molecular") #else: + #Setting natoms_per_particle for atomic and molcular cases + if self.sens_main_is_atomic: + self.sens_main_natoms_per_particle = 1 + else: + self.sens_main_natoms_per_particle = 2 + for i in range(len(self.sens_ref)): + if self.sens_ref_is_atomic[i]: + self.sens_ref[i].natoms_per_particle = 1 + else: + self.sens_ref[i].natoms_per_particle = 2 + if self.atomic_axis: if self.sens_main_is_atomic: #self.atomic_sens = self.sens_main @@ -224,6 +235,14 @@ def InternalRun(self): logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info("***Done printing pre-optimization***") + + #Optimizing the detection threshold for the comparison config files + #Before the density optimization + for i in range(len(self.sens_ref)): + thresh_limits = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] + self.sens_ref[i].sens_with_configured_density_and_opt_thresh = np.min(thresh_limits) + + # create main plot self.create_plot() @@ -244,9 +263,12 @@ def InternalRun(self): logger.info('Adding goal: {} = {}'.format(key, value)) self.add_goal(value, key) + #Add point at configured density if self.density_axis and self.add_point_at_configured_density: - self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color='b', label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) - + self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.main_curve_color, label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) + for i in range(len(self.sens_ref)): + self.ax.scatter([self.sens_ref[i].Experiment.number_density*m**3], [self.sens_ref[i].sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.comparison_curve_colors[i], zorder=3) + # optimize density if self.optimize_main_density: thresh_opt_main = [] @@ -358,11 +380,11 @@ def InternalRun(self): logger.info('Total background: {}/eV/s'.format(self.sens_main.background_rate*eV*s)) logger.info('Total signal: {}'.format(rho*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ - self.sens_main.tau_tritium*2)) + self.sens_main.tau_tritium*self.sens_main_natoms_per_particle)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* rho*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ - self.sens_main.tau_tritium*2)) + self.sens_main.tau_tritium*self.sens_main_natoms_per_particle)) self.sens_main.print_statistics() self.sens_main.print_systematics() @@ -457,11 +479,11 @@ def InternalRun(self): logger.info('Total background: {}/eV/s'.format(self.sens_ref[i].background_rate*eV*s)) logger.info('Total signal: {}'.format(rho2*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ - self.sens_ref[i].tau_tritium*2)) + self.sens_ref[i].tau_tritium*self.sens_ref[i].natoms_per_particle)) logger.info('Signal in last eV: {}'.format(self.sens_ref[i].last_1ev_fraction*eV**3* rho2*self.sens_ref[i].effective_volume* self.sens_ref[i].Experiment.LiveTime/ - self.sens_ref[i].tau_tritium*2)) + self.sens_ref[i].tau_tritium*self.sens_ref[i].natoms_per_particle)) self.sens_ref[i].print_statistics() self.sens_ref[i].print_systematics() @@ -542,7 +564,7 @@ def create_plot(self): ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) - ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + ax.set_ylabel(r"90% CL on $m_\beta$ (eV)") elif self.frequency_axis: @@ -554,7 +576,7 @@ def create_plot(self): ax.set_ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") self.ax2 = ax.twinx() - self.ax2.set_ylabel(r"90% CL $m_\beta$ (eV)") + self.ax2.set_ylabel(r"90% CL on $m_\beta$ (eV)") self.ax2.set_yscale("log") self.ax2.set_ylim(self.ylim) @@ -567,6 +589,10 @@ def create_plot(self): logger.info("Adding livetime axis") ax.set_xlim(self.year_range[0], self.year_range[-1]) axis_label = r"Livetime (years)" + #The annotations below are temporary, for pre-CDR plots + #ax.annotate("1 year", xy=[1, 0.26],xytext=[0.6, 0.16], fontsize=13, arrowprops=dict(arrowstyle="->"), bbox=dict(pad=0, facecolor="none", edgecolor="none")) + #ax.annotate("", xy=[1, 0.11],xytext=[0.8, 0.155], fontsize=13, arrowprops=dict(arrowstyle="->")) + #ax.annotate("8 years", xy=[8, 0.041],xytext=[4, 0.053], fontsize=13, arrowprops=dict(arrowstyle="->"), bbox=dict(pad=0, facecolor="none", edgecolor="none")) elif self.ncavities_livetime_axis: logger.info("Adding ncavities*livetime axis") ax.set_xlim(self.ncavities_livetime_range[0], self.ncavities_livetime_range[-1]) @@ -577,7 +603,7 @@ def create_plot(self): axis_label = r"Number of Cavities $\times$ Efficiency $\times$ Livetime (years)" ax.set_xlabel(axis_label) - ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + ax.set_ylabel(r"90% CL on $m_\beta$ (eV)") ax.set_ylim(self.ylim) self.ax2 = ax.twinx() @@ -620,7 +646,7 @@ def create_plot(self): self.kp_ax[1].set_ylabel(r'Optimum desnity (1/m$^3$)') self.kp_ax[2].set_ylabel(r'Total and effective (dashed) Volume (m$^3$)') self.kp_ax[3].set_ylabel('Noise temperature (K)') - + self.kp_fig.tight_layout() def add_track_length_axis(self): @@ -664,6 +690,7 @@ def add_track_length_axis(self): if not self.molecular_axis and not self.atomic_axis: logger.warning("No track length axis added since neither atomic nor molecular was requested") + self.fig.tight_layout() def add_magnetic_field_axis(self): diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 5cf2de3a..3caf0625 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -704,7 +704,7 @@ def print_SNRs(self, rho=None): logger.info("Noise power in 1eV: {}W".format(self.noise_energy*eV_bandwidth/W)) logger.info("SNRs of carriers (90°, used in calc) for 1eV bandwidth: {}, {}".format(SNR_1eV_90deg, SNR_1eV_ex_carrier)) #logger.info("SNR 1 eV from temperatures:{}".format(self.received_power/(self.noise_energy*eV_bandwidth))) - logger.info("SNRs of carriers (90°, used in calc) for track duration: {}, {}".format(SNR_track_duration_90deg, SNR_track_duration_ex_carrier)) + logger.info("SNRs of carriers (90°, used in calc) for track duration at optimum density: {}, {}".format(SNR_track_duration_90deg, SNR_track_duration_ex_carrier)) logger.info("SNR of carriers (90°, used in calc) for 1 ms: {}, {}".format(SNR_1ms_90deg, SNR_1ms_ex_carrier)) diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 8cc176df..69a20b9f 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -268,7 +268,7 @@ def syst_doppler_broadening(self): mass_T = self.T_mass endpoint = self.T_endpoint - # these factors are mainly neglidible in the recoil equation below + # these factors are mainly negligible in the recoil equation below E_rec = 3.409 * eV # maximal value # same for molecular tritium? mbeta = 0*eV # term neglidible betanu = 1 # neutrinos are fast diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index cf9203d9..00c0d298 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_March-3-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_March-20-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -164,13 +164,13 @@ "y_limits": [2e-2, 4], "density_range": [3e14,3e18], #5e13 "det_thresh_range": [5, 115], - "main_curve_upper_label": r"LFA (Phase III): 560 MHz", #Phase III scenario: 1 GHz", + "main_curve_upper_label": r"LFA (Phase III), threshold scenario", #560 MHz #Phase III scenario: 1 GHz", "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "comparison_curve": True, "main_curve_color": "blue", - "comparison_curve_colors": ["red", "black"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase IV scenario: 150 MHz", r"One Phase IV cavity"], + "comparison_curve_colors": ["black", "red"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"Module #1 of Phase IV", r"Ten Phase IV cavities"], #: 150 MHz "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, @@ -181,15 +181,15 @@ "plot_key_parameters": True, "goals_y_rel_position": 0.79 #0.755 } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_ncav-eff-time_curve_target_March-3-2025.pdf", #ncav-eff-time + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_v1_March-17-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -201,34 +201,79 @@ "atomic_axis": False, "exposure_axis": False, "density_axis": False, - "ncav_eff_time_axis": True, + "ncav_eff_time_axis": False, "ncavities_livetime_axis": False, - "livetime_axis": False, + "livetime_axis": True, "cavity": True, "add_PhaseII": False, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", - "add_1year_1cav_point_to_last_ref": True, + "add_1year_1cav_point_to_last_ref": False, "y_limits": [2.5e-2, 1], #"density_range": [1e12,1e19], - "year_range": [0.1,10**3], - "main_curve_upper_label": r"LFA (Phase III): 560 MHz, V = 1.7 m$^3$", - "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, + "year_range": [0.1,25], + "main_curve_upper_label": r"LFA (Phase III): $1.7\,$m$^3$, 1 year", # 560 MHz, $V = 1.7\,$m$^3$ + "goals": {"LFA threshold (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Phase IV scenario: 150 MHz, V = 94 m$^3 \times 10$"], #, "One Phase IV cavity"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r'Module #1 of Phase IV: $94\,$m$^3$, 1 year', r"Ten Phase IV cavities: $940\,$m$^3$, 8 years"], #150 MHz, $V = 94\,$m$^3$ "main_curve_color": "blue", - "comparison_curve_colors": ["red", "black"], + "comparison_curve_colors": ["black", "red"], "optimize_main_density": False, - "optimize_comparison_density": False, + "optimize_comparison_density": True, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, - "goals_x_position": 4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + "goals_x_position": 0.35, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime "goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() + + +sens_config_dict = { + # required + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./PhaseIV_scenario_sensitivity_vs_livetime_curve_March-4-2025.pdf", #ncav-eff-time + "exposure_axis": True, + # optional + "figsize": (8.3, 6.3), #(10,6), + "fontsize": 15, + "legend_location": "upper right", + "legend_bbox_to_anchor": (-0.,0,0.86,0.955), + "track_length_axis": False, + "molecular_axis": False, + "atomic_axis": False, + "exposure_axis": False, + "density_axis": False, + "ncav_eff_time_axis": False, + "ncavities_livetime_axis": False, + "livetime_axis": True, + "cavity": True, + "add_PhaseII": False, + "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", + "add_1year_1cav_point_to_last_ref": False, + "y_limits": [1e-2, 1.5e-1], + #"density_range": [1e12,1e19], + "year_range": [0.1,5e2], + "main_curve_upper_label": r"Default Phase IV scenario", + "goals": {"Phase IV (0.04 eV)": 0.04}, + "comparison_curve": True, + "comparison_config_file_path": ["/termite/sensitivity_config_files/P4_scenario_USR_sigmaeR.cfg", "/termite/sensitivity_config_files/P4_scenario_USR_nonexposure.cfg"], + "comparison_curve_label": [r"Less field broadening, fewer events", r"Cavity magic"], #, "One Phase IV cavity"], + "main_curve_color": "red", + "comparison_curve_colors": ["darkorange", "purple"], + "optimize_main_density": False, + "optimize_comparison_density": True, + "lower_label_y_position": 0.17, + "upper_label_y_position": 0.7, + "label_x_position": 0.115, + "goals_x_position": 1.1e-1, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + "goals_y_rel_position": 0.89, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime + } +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() From aff7ed099d4b347e4364120fe5ff5506952ecd25 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 20 Mar 2025 07:56:24 -0700 Subject: [PATCH 248/262] In parameter scan processor, fixed event rate calcs to use *2 for T2 and *1 for T; changed y-label for consistency --- .../CavitySensitivityCurveProcessor.py | 3 ++- .../SensitivityParameterScanProcessor.py | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 3a5bb38e..a6ddab60 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -167,7 +167,8 @@ def InternalConfigure(self, params): #logger.info("Comparison curve is molecular") #else: - #Setting natoms_per_particle for atomic and molcular cases + # Setting natoms_per_particle for atomic and molecular cases, to use later + # when calculating and printing event rates if self.sens_main_is_atomic: self.sens_main_natoms_per_particle = 1 else: diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 10f37705..adfae8db 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -122,6 +122,14 @@ def InternalConfigure(self, params): self.sens_main_is_atomic = self.sens_main.Experiment.atomic + # Setting natoms_per_particle for atomic and molecular cases, to use later + # when calculating and printing event rates + if self.sens_main_is_atomic: + self.sens_main_natoms_per_particle = 1 + else: + self.sens_main_natoms_per_particle = 2 + + # check atomic and molecular if self.molecular_axis: if not self.sens_main_is_atomic: @@ -265,11 +273,11 @@ def InternalRun(self): logger.info('T2 in Veff: {}'.format(rho_opt*self.sens_main.effective_volume)) logger.info('Total signal: {}'.format(rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ - self.sens_main.tau_tritium*2)) + self.sens_main.tau_tritium*self.sens_main_natoms_per_particle)) logger.info('Signal in last eV: {}'.format(self.sens_main.last_1ev_fraction*eV**3* rho_opt*self.sens_main.effective_volume* self.sens_main.Experiment.LiveTime/ - self.sens_main.tau_tritium*2)) + self.sens_main.tau_tritium*self.sens_main_natoms_per_particle)) self.sens_main.print_statistics() systematic_limit, total_sigma = self.sens_main.print_systematics() @@ -301,7 +309,7 @@ def InternalRun(self): #plt.title("Sensitivity vs. {}".format(self.scan_parameter_name)) plt.plot(self.scan_parameter_values/self.scan_parameter_unit, np.array(self.optimum_limits)/eV, marker=".", label="Density optimized scenarios") plt.xlabel(f"{param} ({self.scan_parameter_unit_string})", fontsize=self.fontsize) - plt.ylabel(r"90% CL $m_\beta$ (eV)", fontsize=self.fontsize) + plt.ylabel(r"90% CL on $m_\beta$ (eV)", fontsize=self.fontsize) if self.plot_sensitivity_scan_on_log_scale: plt.yscale("log") # TODO log x here @@ -345,7 +353,7 @@ def create_plot(self, param_range=[]): ax.set_xlabel(axis_label) ax.set_ylim(self.ylim) - ax.set_ylabel(r"90% CL $m_\beta$ (eV)") + ax.set_ylabel(r"90% CL on $m_\beta$ (eV)") if len(param_range)>4: # add colorbar with colors from self.range From ade381b6a75c8787531a65cfd9d53c092a432d60 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Mon, 24 Mar 2025 14:14:14 -0700 Subject: [PATCH 249/262] Added a call to Print_Efficiencies in scan processor. --- .../processors/Sensitivity/SensitivityParameterScanProcessor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 10f37705..30cc34a9 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -271,6 +271,7 @@ def InternalRun(self): self.sens_main.Experiment.LiveTime/ self.sens_main.tau_tritium*2)) + self.sens_main.print_Efficiencies() self.sens_main.print_statistics() systematic_limit, total_sigma = self.sens_main.print_systematics() self.sys_lim.append(systematic_limit) From 46f6b997712cc47f90bf7e890dfef375cf8c1156 Mon Sep 17 00:00:00 2001 From: benanator77 Date: Mon, 7 Apr 2025 13:18:45 -0700 Subject: [PATCH 250/262] Separated cavity formulas init, only reads config in init, default value calculations now go in a separate function --- .../Sensitivity/SensitivityParameterScanProcessor.py | 5 +++++ mermithid/sensitivity/SensitivityCavityFormulas.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py index 8a991127..9be815bd 100644 --- a/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py +++ b/mermithid/processors/Sensitivity/SensitivityParameterScanProcessor.py @@ -192,7 +192,12 @@ def InternalRun(self): logger.error(f"Parameter {param} not found in {category}") raise e + + # Set to scan param value self.sens_main.__dict__[category].__dict__[param] = parameter_value + # Re-calc the cavity init with the new param + self.sens_main.CalcDefaults(overwrite=True) + # Ensure param scan value unchanged read_back = self.sens_main.__dict__[category].__dict__[param] #setattr(self.sens_main, self.scan_parameter_name, parameter_value) #read_back = getattr(self.sens_main, self.scan_parameter_name) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 3caf0625..147b4e09 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -183,7 +183,7 @@ def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_f -###############################################################################cd +############################################################################### class CavitySensitivity(Sensitivity): """ Documentation: @@ -194,7 +194,13 @@ class CavitySensitivity(Sensitivity): """ def __init__(self, config_path): Sensitivity.__init__(self, config_path) - + + # Calc non-config parameters outside of init function: + ## Allows re-calcing params if config values changed later, e.g. param scans + self.CalcDefaults(overwrite=False) + + # Add any additional initialization to this function, NOT __INIT__!! + def CalcDefaults(self, overwrite=False): ### #Initialization related to the effective volume: ### @@ -203,7 +209,7 @@ def __init__(self, config_path): self.CavityRadius() #Get trap length from cavity length if not specified - if not hasattr(self.Experiment, 'trap_length'): + if ((not hasattr(self.Experiment, 'trap_length')) or overwrite): self.Experiment.trap_length = 0.8 * 2 * self.cavity_radius * self.Experiment.cavity_L_over_D logger.info("Calc'd trap length: {} m".format(round(self.Experiment.trap_length/m, 3), 2)) From 70b2560dd216f7da72cc7b6de54f827afc1c4265 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 17 Apr 2025 16:01:24 -0400 Subject: [PATCH 251/262] Made more aspects of sensitivity plotting configurable from a dictionary outside of mermithid --- .../CavitySensitivityCurveProcessor.py | 60 ++++++++++--------- .../sensitivity/SensitivityCavityFormulas.py | 7 ++- test_analysis/Cavity_Sensitivity_analysis.py | 58 ++++++++++-------- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index a6ddab60..6c01d35f 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -3,7 +3,7 @@ Author: C. Claessens, T. E. Weiss Date: 06/07/2023 -Updated: February 2025 +Updated: April 2025 ''' from __future__ import absolute_import @@ -61,8 +61,12 @@ def InternalConfigure(self, params): self.main_curve_upper_label = reader.read_param(params, 'main_curve_upper_label', r"molecular"+"\n"+r"$V_\mathrm{eff} = 2\, \mathrm{cm}^3$"+"\n"+r"$\sigma_B = 7\,\mathrm{ppm}$") self.main_curve_lower_label = reader.read_param(params, 'main_curve_lower_label', r"$\sigma_B = 1\,\mathrm{ppm}$") self.comparison_curve_label = reader.read_param(params, 'comparison_curve_label', r"atomic"+"\n"+r"$V_\mathrm{eff} = 5\, \mathrm{m}^3$"+"\n"+r"$\sigma_B = 0.13\,\mathrm{ppm}$") - self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) self.main_curve_color = reader.read_param(params, 'main_curve_color', "darkblue") + self.comparison_curve_colors = reader.read_param(params,'comparison_curve_colors', ["blue", "darkred", "red"]) + self.main_curve_linestyle = reader.read_param(params, 'main_curve_linestyle', "solid") + self.comparison_curve_linestyles = reader.read_param(params,'comparison_curve_linestyles', ["solid", "solid", "solid"]) + self.main_curve_marker = reader.read_param(params, 'main_curve_marker', "d") + self.comparison_curve_markers = reader.read_param(params,'comparison_curve_markers', ["d", "d", "d"]) # options self.optimize_main_density = reader.read_param(params, 'optimize_main_density', True) @@ -102,7 +106,8 @@ def InternalConfigure(self, params): self.label_x_position = reader.read_param(params, 'label_x_position', 5e19) self.upper_label_y_position = reader.read_param(params, 'upper_label_y_position', 5) self.lower_label_y_position = reader.read_param(params, 'lower_label_y_position', 2.3) - self.goal_x_pos = reader.read_param(params, "goals_x_position", 1e14) + self.goal_x_pos = reader.read_param(params, "goals_x_position", {"LFA threshold (0.7 eV)": 0.11, "Phase IV (0.04 eV)": 0.11}) + self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", {"LFA threshold (0.7 eV)": 0.855, "Phase IV (0.04 eV)": 0.855}) self.comparison_label_y_position = reader.read_param(params, 'comparison_label_y_position', 0.044) self.comparison_label_x_position = reader.read_param(params, 'comparison_label_x_position', 5e16) @@ -111,7 +116,6 @@ def InternalConfigure(self, params): self.add_PhaseII = reader.read_param(params, "add_PhaseII", False) self.add_point_at_configured_density = reader.read_param(params, "add_point_at_configured_density", True) - self.goals_y_rel_position = reader.read_param(params, "goals_y_rel_position", 0.75) self.add_1year_1cav_point_to_last_ref = reader.read_param(params, "add_1year_1cav_point_to_last_ref", False) # key parameter plots @@ -246,7 +250,7 @@ def InternalRun(self): # create main plot self.create_plot() - + # optionally add Phase II curve and point to exposure plot if self.exposure_axis and self.add_PhaseII: self.add_Phase_II_exposure_sens_line(self.sens_PhaseII) @@ -262,13 +266,13 @@ def InternalRun(self): # add goals for key, value in self.goals.items(): logger.info('Adding goal: {} = {}'.format(key, value)) - self.add_goal(value, key) + self.add_goal(value, key, self.goal_x_pos[key], self.goals_y_rel_position[key]) #Add point at configured density if self.density_axis and self.add_point_at_configured_density: - self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.main_curve_color, label="Operating density", zorder=3) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) + self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.main_curve_color, label="Operating density", zorder=4) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) for i in range(len(self.sens_ref)): - self.ax.scatter([self.sens_ref[i].Experiment.number_density*m**3], [self.sens_ref[i].sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.comparison_curve_colors[i], zorder=3) + self.ax.scatter([self.sens_ref[i].Experiment.number_density*m**3], [self.sens_ref[i].sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.comparison_curve_colors[i], zorder=4) # optimize density if self.optimize_main_density: @@ -323,13 +327,13 @@ def InternalRun(self): self.sens_main.MagneticField.sigmaer = self.sigmae_theta_r * eV self.sens_main.MagneticField.sigmae_theta = 0 * eV if self.livetime_axis: - self.add_sens_line(self.sens_main, exposure_type="livetime", color=self.main_curve_color, label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, exposure_type="livetime", color=self.main_curve_color, marker=self.main_curve_marker, linestyle=self.main_curve_linestyle, label=self.main_curve_upper_label) elif self.ncavities_livetime_axis: - self.add_sens_line(self.sens_main, exposure_type="ncavities_livetime", color=self.main_curve_color, label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, exposure_type="ncavities_livetime", color=self.main_curve_color, marker=self.main_curve_marker, linestyle=self.main_curve_linestyle, label=self.main_curve_upper_label) elif self.ncav_eff_time_axis: - self.add_sens_line(self.sens_main, exposure_type="ncav_eff_time", color=self.main_curve_color, label=self.main_curve_upper_label) + self.add_sens_line(self.sens_main, exposure_type="ncav_eff_time", color=self.main_curve_color, marker=self.main_curve_marker, linestyle=self.main_curve_linestyle, label=self.main_curve_upper_label) else: - self.add_sens_line(self.sens_main, color=self.main_curve_color, label=self.main_curve_upper_label, zorder=2) + self.add_sens_line(self.sens_main, color=self.main_curve_color, linestyle=self.main_curve_linestyle, label=self.main_curve_upper_label, zorder=3) # PRINT OPTIMUM RESULTS @@ -492,13 +496,13 @@ def InternalRun(self): #Add comparison curves label=self.comparison_curve_label if self.livetime_axis: - limits = self.add_sens_line(self.sens_ref[i], exposure_type="livetime", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + limits = self.add_sens_line(self.sens_ref[i], exposure_type="livetime", plot_key_params=False, color=self.comparison_curve_colors[i], marker=self.comparison_curve_markers[i], linestyle=self.comparison_curve_linestyles[i], label=label[i]) elif self.ncavities_livetime_axis: - limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncavities_livetime", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncavities_livetime", plot_key_params=False, color=self.comparison_curve_colors[i], marker=self.comparison_curve_markers[i], linestyle=self.comparison_curve_linestyles[i], label=label[i]) elif self.ncav_eff_time_axis: - limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncav_eff_time", plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i]) + limits = self.add_sens_line(self.sens_ref[i], exposure_type="ncav_eff_time", plot_key_params=False, color=self.comparison_curve_colors[i], marker=self.comparison_curve_markers[i], linestyle=self.comparison_curve_linestyles[i], label=label[i]) else: - limits = self.add_sens_line(self.sens_ref[i], plot_key_params=False, color=self.comparison_curve_colors[i], label=label[i], zorder=5) + limits = self.add_sens_line(self.sens_ref[i], plot_key_params=False, color=self.comparison_curve_colors[i], linestyle=self.comparison_curve_linestyles[i], label=label[i], zorder=5) #self.ax.text(self.comparison_label_x_position[a], self.comparison_label_y_position[a], label[a], color=colors[a], fontsize=9.5) if self.exposure_axis or self.livetime_axis or self.ncavities_livetime_axis or self.ncav_eff_time_axis: @@ -557,7 +561,7 @@ def create_plot(self): if self.atomic_axis and self.molecular_axis: axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" elif self.atomic_axis: - axis_label = r"Atom number density in electron trap$\, \, (\mathrm{m}^{-3})$" + axis_label = r"Average atom number density in electron trap$\, \, (\mathrm{m}^{-3})$" elif self.molecular_axis: axis_label = r"Molecular number density $n\, \, (\mathrm{m}^{-3})$" else: @@ -734,9 +738,9 @@ def get_relative(val, axis): ) """ - def add_goal(self, value, label): - self.ax.axhline(value, color="gray", ls="--", zorder=1) - self.ax.text(self.goal_x_pos, self.goals_y_rel_position*value, label, fontsize=self.fontsize - 3) + def add_goal(self, value, label, x_pos, y_rel_position): + self.ax.axhline(value, color="darkgray", ls="solid", linewidth="1", zorder=1) + self.ax.text(x_pos, y_rel_position*value, label, fontsize=self.fontsize - 3) def add_density_sens_line(self, sens, plot_key_params=False, **kwargs): limits = [] @@ -844,43 +848,43 @@ def add_exposure_sens_line(self, sens, exposure_type=None, plot_key_params=False limits = [] if exposure_type=="livetime": #self.ax.scatter([sens.Experiment.livetime/year], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) - self.ax.scatter([sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, zorder=20, marker=kwargs["marker"], color=kwargs["color"], label=kwargs["label"]) for lt in self.years: sens.Experiment.livetime = lt #limits.append(sens.sensitivity()/eV**2) limits.append(sens.CL90()/eV) - self.ax.plot(self.years/year, limits, color=kwargs["color"]) + self.ax.plot(self.years/year, limits, color=kwargs["color"], linestyle=kwargs["linestyle"]) elif exposure_type=="ncavities_livetime": - self.ax.scatter([sens.Experiment.n_cavities*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([sens.Experiment.n_cavities*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, zorder=20, marker=kwargs["marker"], color=kwargs["color"], label=kwargs["label"]) ncavities_livetime = [] year_list = self.years/sens.Experiment.n_cavities for lt in year_list: sens.Experiment.livetime = lt ncavities_livetime.append(sens.Experiment.n_cavities*lt/year) limits.append(sens.CL90()/eV) - self.ax.plot(ncavities_livetime, limits, color=kwargs["color"]) + self.ax.plot(ncavities_livetime, limits, color=kwargs["color"], linestyle=kwargs["linestyle"]) elif exposure_type=="ncav_eff_time": ncav_eff = sens.EffectiveVolume()/sens.TrapVolume()*sens.Experiment.n_cavities - self.ax.scatter([ncav_eff*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([ncav_eff*sens.Experiment.livetime/year], [limit_inputted_exposure], s=40, zorder=20, marker=kwargs["marker"], color=kwargs["color"], label=kwargs["label"]) ncav_eff_time = [] year_list = self.ncav_eff_times/ncav_eff for lt in year_list: sens.Experiment.livetime = lt ncav_eff_time.append(ncav_eff*lt/year) limits.append(sens.CL90()/eV) - self.ax.plot(ncav_eff_time, limits, color=kwargs["color"]) + self.ax.plot(ncav_eff_time, limits, color=kwargs["color"], linestyle=kwargs["linestyle"]) else: years = [] standard_exposure = sens.EffectiveVolume()*sens.Experiment.livetime/m**3/year #self.ax.scatter([standard_exposure], [np.min(sigma_mbeta)], s=40, marker="d", zorder=20, **kwargs) - self.ax.scatter([standard_exposure], [limits], s=40, marker="d", zorder=20, **kwargs) + self.ax.scatter([standard_exposure], [limits], s=40, zorder=20, marker=kwargs["marker"], color=kwargs["color"], label=kwargs["label"]) for ex in self.exposures: lt = ex/sens.EffectiveVolume() years.append(lt/year) sens.Experiment.livetime = lt #sigma_mbetas.append(sens.sensitivity()/eV**2) limits.append(sens.CL90()/eV) - self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) + self.ax.plot(self.exposures/m**3/year, limits, color=kwargs["color"], linestyle=kwargs["linestyle"]) #label="{} density = {:.1e} {}".format(gas, rho_opt*m**3, unit)) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 3caf0625..10b6cbf3 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -236,7 +236,7 @@ def __init__(self, config_path): #Calculate the effective volume and print out related quantities self.EffectiveVolume() logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3), 2)) - logger.info("Total trap volume: {} m^3".format(round(self.total_trap_volume/m**3), 3)) + logger.info("Total trap volume: {} m^3".format(self.total_trap_volume/m**3)) logger.info("Cyclotron radius: {}m".format(self.cyc_rad/m)) if self.use_cyc_rad: logger.info("Using cyclotron radius as unusable distance from wall, for radial efficiency calculation") @@ -299,6 +299,7 @@ def EffectiveVolume(self): if self.Efficiency.usefixedvalue: self.effective_volume = self.total_trap_volume * self.Efficiency.fixed_efficiency self.use_cyc_rad = False + self.RF_background_rate_per_eV = self.Experiment.RF_background_rate_per_eV else: #Detection efficiency if self.Threshold.use_detection_threshold: @@ -479,7 +480,9 @@ def frequency_variance_from_CRLB(self, tau_SNR): # The first term relies on the relation delta_t_start = sqrt(20)*tau_snr. This is from Equation 6.40 of Nick's thesis, # derived in Appendix A and verified with an MC study. # Using a factor of 23 instead of 20, from re-calculating Nick's integrals (though this derivation is approximate). - # Nick's derivation uses an expression for P_fa - we need to check if it's consistent with what Rene uses now. + # Nick's derivation uses an expression for P_fa assuming the phase is known. + # The phase won't be known, but it's more difficult to determine the unknown-phase expression. + # Working on that. return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + 96*tau_SNR/self.time_window**3)/(2*np.pi)**2 diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 00c0d298..4650413b 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,8 +148,8 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_threshold_March-20-2025.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-17-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -161,25 +161,27 @@ "density_axis": True, "track_length_axis": True, "effs_for_sampled_radii": True, - "y_limits": [2e-2, 4], + "y_limits": [2e-2, 6.5], "density_range": [3e14,3e18], #5e13 "det_thresh_range": [5, 115], - "main_curve_upper_label": r"LFA (Phase III), threshold scenario", #560 MHz #Phase III scenario: 1 GHz", - "goals": {"LFA (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "main_curve_upper_label": r"LFA, threshold scenario", #560 MHz #Phase III scenario: 1 GHz", + "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, + "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, + "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 "comparison_curve": True, "main_curve_color": "blue", - "comparison_curve_colors": ["black", "red"], - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"Module #1 of Phase IV", r"Ten Phase IV cavities"], #: 150 MHz + "comparison_curve_colors": ["blue", "darkred", "black"], + "main_curve_linestyle": "dashed", + "comparison_curve_linestyles": ["solid", "dotted", "dashdot"], + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], + "comparison_curve_label": [r"LFA, target scenario", r"Module #1 of Phase IV", r"Ten Phase IV cavities"], #: 150 MHz "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 4e14, #4e14, #0.02, #1e14, - "goals_x_position": 6e14, #3.3e14, #5.5e13, "plot_key_parameters": True, - "goals_y_rel_position": 0.79 #0.755 } sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") sens_curve.Configure(sens_config_dict) @@ -188,14 +190,14 @@ sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_v1_March-17-2025.pdf", #ncav-eff-time + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", + "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_v2_April-17-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), "fontsize": 15, "legend_location": "upper right", - "legend_bbox_to_anchor": (-0.,0,0.86,0.955), + "legend_bbox_to_anchor": (-0.,0,0.86,0.971), #last entry: 0.955 "track_length_axis": False, "molecular_axis": False, "atomic_axis": False, @@ -208,27 +210,33 @@ "add_PhaseII": False, "PhaseII_config_path": "/termite/sensitivity_config_files/Config_PhaseII_Experiment.cfg", "add_1year_1cav_point_to_last_ref": False, - "y_limits": [2.5e-2, 1], + "y_limits": [2e-2, 3], #"density_range": [1e12,1e19], - "year_range": [0.1,25], - "main_curve_upper_label": r"LFA (Phase III): $1.7\,$m$^3$, 1 year", # 560 MHz, $V = 1.7\,$m$^3$ - "goals": {"LFA threshold (0.45 eV)": 0.45, "Phase IV (0.04 eV)": 0.04}, + "year_range": [0.1,35], + "main_curve_upper_label": r"LFA, threshold: $1.7\,$m$^3$, 1 yr", # 560 MHz, $V = 1.7\,$m$^3$ + "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, + "goals_x_position": {"LFA threshold (0.7 eV)": 0.108, "Phase IV (0.04 eV)": 0.108}, #6e14, #3.3e14, #5.5e13, + "goals_y_rel_position": {"LFA threshold (0.7 eV)": 0.83, "Phase IV (0.04 eV)": 0.83}, #6e14, #3.3e14, #5.5e13, "comparison_curve": True, - "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r'Module #1 of Phase IV: $94\,$m$^3$, 1 year', r"Ten Phase IV cavities: $940\,$m$^3$, 8 years"], #150 MHz, $V = 94\,$m$^3$ + "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", + "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'Module #1 of Phase IV: $94\,$m$^3$, 1 yr', r"Ten Phase IV cavities: $940\,$m$^3$, 8 yrs"], #150 MHz, $V = 94\,$m$^3$ "main_curve_color": "blue", - "comparison_curve_colors": ["black", "red"], + "comparison_curve_colors": ["blue", "darkred", "black"], + "main_curve_linestyle": "dashed", + "comparison_curve_linestyles": ["solid", "dotted", "dashdot"], + "main_curve_marker": "d", + "comparison_curve_markers": ["o", "^", "X"], "optimize_main_density": False, "optimize_comparison_density": True, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, - "goals_x_position": 0.35, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime - "goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime + #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime + #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { From 06503dde8274f3f1d8e6b2bae5dc526889273282 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 17 Apr 2025 23:22:29 -0400 Subject: [PATCH 252/262] Fixed issue with comparison curve detection threshold optimization for case where density isn't optimized --- .../CavitySensitivityCurveProcessor.py | 53 +++++++++++++++++-- test_analysis/Cavity_Sensitivity_analysis.py | 10 ++-- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 6c01d35f..7407070e 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -418,15 +418,58 @@ def InternalRun(self): plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") plt.legend() plt.tight_layout() - plt.savefig("stat_and_syst_vs_density.pdf") + plt.savefig("stat_and_syst_vs_density_LFA_threshold.png", dpi=300) fig = plt.figure() plt.loglog(self.rhos*m**3, sigma_startf/eV) plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") plt.ylabel(r"Resolution from $f$ reconstruction, axial field (eV)") plt.tight_layout() - plt.savefig("resolution_from_CRLB_vs_density.pdf") - + plt.savefig("resolution_from_CRLB_vs_density_LFA_threshold.png", dpi=300) + """ + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + for i in range(len(self.rhos)): + temp_rho = deepcopy(self.sens_ref[1].Experiment.number_density) + self.sens_ref[1].Experiment.number_density = self.rhos[i] + self.sens_ref[1].Threshold.detection_threshold = thresh_opt_main[i] + labels, sigmas, deltas = self.sens_ref[1].get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_ref[1].StatSens()) + syst_on_mbeta2.append(self.sens_ref[1].SystSens()) + self.sens_ref[1].Experiment.number_density = temp_rho + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig("stat_and_syst_vs_density_module1ofPhaseIV.png") + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = [], [], [] + for i in range(len(self.rhos)): + temp_rho = deepcopy(self.sens_ref[2].Experiment.number_density) + self.sens_ref[2].Experiment.number_density = self.rhos[i] + self.sens_ref[2].Threshold.detection_threshold = thresh_opt_main[i] + labels, sigmas, deltas = self.sens_ref[2].get_systematics() + sigma_startf.append(sigmas[1]) + stat_on_mbeta2.append(self.sens_ref[2].StatSens()) + syst_on_mbeta2.append(self.sens_ref[2].SystSens()) + self.sens_ref[2].Experiment.number_density = temp_rho + + sigma_startf, stat_on_mbeta2, syst_on_mbeta2 = np.array(sigma_startf), np.array(stat_on_mbeta2), np.array(syst_on_mbeta2) + fig = plt.figure() + plt.loglog(self.rhos*m**3, stat_on_mbeta2/eV**2, label='Statistical uncertainty') + plt.loglog(self.rhos*m**3, syst_on_mbeta2/eV**2, label='Systematic uncertainty') + plt.xlabel(r"Number density $n\, \, (\mathrm{m}^{-3})$") + plt.ylabel(r"Standard deviation in $m_\beta^2$ (eV$^2$)") + plt.legend() + plt.tight_layout() + plt.savefig("stat_and_syst_vs_density_10PhaseIVcavities.png") + """ + # Optimize comparison curves over density if self.comparison_curve: for i in range(len(self.sens_ref)): @@ -448,10 +491,10 @@ def InternalRun(self): logger.info("Optimized thresholds at each density: {}".format(thresh_opt_comparison)) logger.info("Optimized threshold at optimum n: {}".format(thresh_opt_comparison[limit2_index])) else: - limit_ref = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] + thresh_limits = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] index2 = np.argmin(thresh_limits) self.sens_ref[i].Threshold.detection_threshold = self.thresholds[index2] - limit2 = limit_ref[index2] + limit2 = thresh_limits[index2] if self.optimize_comparison_density: logger.info('COMPARISON CURVE at optimum density:') diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 4650413b..4b5be3d7 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,8 +148,8 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-17-2025.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./Correct_LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-17-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -191,7 +191,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_v2_April-17-2025.pdf", #ncav-eff-time + "plot_path": "./Correct_lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_April-17-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -215,7 +215,7 @@ "year_range": [0.1,35], "main_curve_upper_label": r"LFA, threshold: $1.7\,$m$^3$, 1 yr", # 560 MHz, $V = 1.7\,$m$^3$ "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, - "goals_x_position": {"LFA threshold (0.7 eV)": 0.108, "Phase IV (0.04 eV)": 0.108}, #6e14, #3.3e14, #5.5e13, + "goals_x_position": {"LFA threshold (0.7 eV)": 4.5, "Phase IV (0.04 eV)": 0.108}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 0.83, "Phase IV (0.04 eV)": 0.83}, #6e14, #3.3e14, #5.5e13, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", @@ -227,7 +227,7 @@ "main_curve_marker": "d", "comparison_curve_markers": ["o", "^", "X"], "optimize_main_density": False, - "optimize_comparison_density": True, + "optimize_comparison_density": False, "lower_label_y_position": 0.17, "upper_label_y_position": 0.7, "label_x_position": 0.115, From f293996192597b3aabf97cb2db0158db1a0d43fc Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 17 Apr 2025 23:29:46 -0400 Subject: [PATCH 253/262] Finished swapping back LFA target and threshold curves in the sensitivity vs. density plot --- test_analysis/Cavity_Sensitivity_analysis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 4b5be3d7..38ef675c 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,7 +148,7 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", "plot_path": "./Correct_LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-17-2025.pdf", # optional "figsize": (7.5,6.4), @@ -234,9 +234,9 @@ #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { From df47f1e8e24dd855be82fa23a933919a74e4d9b4 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 7 May 2025 10:26:40 -0400 Subject: [PATCH 254/262] Added eta/(1-eta) term to chirp CRLB for more accurate results in the high-slope case --- .../CavitySensitivityCurveProcessor.py | 2 +- .../sensitivity/SensitivityCavityFormulas.py | 6 ++++-- test_analysis/Cavity_Sensitivity_analysis.py | 20 +++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 7407070e..ffcd0385 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -604,7 +604,7 @@ def create_plot(self): if self.atomic_axis and self.molecular_axis: axis_label = r"(Atomic / molecular) number density $n\, \, (\mathrm{m}^{-3})$" elif self.atomic_axis: - axis_label = r"Average atom number density in electron trap$\, \, (\mathrm{m}^{-3})$" + axis_label = r"Average atom number density in sensitive volume$\, \, (\mathrm{m}^{-3})$" elif self.molecular_axis: axis_label = r"Molecular number density $n\, \, (\mathrm{m}^{-3})$" else: diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index aa40cca7..21378e0f 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -482,14 +482,16 @@ def frequency_variance_from_CRLB(self, tau_SNR): # Applies for a complex signal. return self.FrequencyExtraction.CRLB_scaling_factor*(6*tau_SNR/self.time_window**3)/(2*np.pi)**2 else: - # Non-zero, fitted slope. Still assumes that alpha*T/2 << omega_c. + # Non-zero, fitted slope. + # Doesn't assume that alpha*T/2 << omega_c, since it includes the 5*eta/(1-eta) term in Eq. 25 of Joe's write-up: https://3.basecamp.com/3700981/buckets/3107037/documents/6331876030. + # CODE IMPLEMENTATION NEEDS TO BE DOUBLE-CHECKED BY CONSIDERING AN EXPERIMENT WITH LARGE-ISH ETA. # The first term relies on the relation delta_t_start = sqrt(20)*tau_snr. This is from Equation 6.40 of Nick's thesis, # derived in Appendix A and verified with an MC study. # Using a factor of 23 instead of 20, from re-calculating Nick's integrals (though this derivation is approximate). # Nick's derivation uses an expression for P_fa assuming the phase is known. # The phase won't be known, but it's more difficult to determine the unknown-phase expression. # Working on that. - return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + 96*tau_SNR/self.time_window**3)/(2*np.pi)**2 + return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + tau_SNR/self.time_window**3*(96 + 6*5*self.eta/(1-self.eta)))/(2*np.pi)**2 def syst_frequency_extraction(self): diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 38ef675c..d0512997 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./Correct_LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-17-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-18-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -174,7 +174,7 @@ "main_curve_linestyle": "dashed", "comparison_curve_linestyles": ["solid", "dotted", "dashdot"], "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], - "comparison_curve_label": [r"LFA, target scenario", r"Module #1 of Phase IV", r"Ten Phase IV cavities"], #: 150 MHz + "comparison_curve_label": [r"LFA, target scenario", r"One full-size module", r"Phase IV: Ten full-size modules"], #: 150 MHz "comparison_label_y_position": [2, 0.105, 0.046], #[2, 0.105, 0.046], "comparison_label_x_position": [4.5e15, 7e14, 7e14], #[4.5e15, 2.2e16, 1e15], #"sigmae_theta_r": 0.159, @@ -183,15 +183,15 @@ "label_x_position": 4e14, #4e14, #0.02, #1e14, "plot_key_parameters": True, } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./Correct_lfa_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_April-17-2025.pdf", #ncav-eff-time + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_April-18-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -219,7 +219,7 @@ "goals_y_rel_position": {"LFA threshold (0.7 eV)": 0.83, "Phase IV (0.04 eV)": 0.83}, #6e14, #3.3e14, #5.5e13, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", - "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'Module #1 of Phase IV: $94\,$m$^3$, 1 yr', r"Ten Phase IV cavities: $940\,$m$^3$, 8 yrs"], #150 MHz, $V = 94\,$m$^3$ + "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'One full-size module: $94\,$m$^3$, 1 yr', r"Phase IV$-$Ten full-size modules: $940\,$m$^3$, 8 yrs"], #150 MHz, $V = 94\,$m$^3$ "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", @@ -234,9 +234,9 @@ #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { From 136254cb50bdaf57b1df2e1ece8382d2d5feb9e2 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 2 Jul 2025 16:10:51 -0400 Subject: [PATCH 255/262] Updated eta-dependent term of CRLB formula Given sign error caught by Jin. Joe then fixed the derivation. --- mermithid/sensitivity/SensitivityCavityFormulas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index 12ccadc0..a80be149 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -491,7 +491,7 @@ def frequency_variance_from_CRLB(self, tau_SNR): # Nick's derivation uses an expression for P_fa assuming the phase is known. # The phase won't be known, but it's more difficult to determine the unknown-phase expression. # Working on that. - return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + tau_SNR/self.time_window**3*(96 + 6*5*self.eta/(1-self.eta)))/(2*np.pi)**2 + return self.FrequencyExtraction.CRLB_scaling_factor*(23*(self.slope*tau_SNR)**2 + tau_SNR/self.time_window**3*(96 - 6*5*self.eta/(1+self.eta)))/(2*np.pi)**2 def syst_frequency_extraction(self): From 167aae442d5cd73fac06ad5c84b342c933876bb2 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 21 Nov 2025 16:35:49 -0500 Subject: [PATCH 256/262] 100m^3 cavities in sensitivity vs. livetime and density plots --- test_analysis/Cavity_Sensitivity_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index d0512997..3b8d2845 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_April-18-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -191,7 +191,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_April-18-2025.pdf", #ncav-eff-time + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Nov-21-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -219,7 +219,7 @@ "goals_y_rel_position": {"LFA threshold (0.7 eV)": 0.83, "Phase IV (0.04 eV)": 0.83}, #6e14, #3.3e14, #5.5e13, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", - "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'One full-size module: $94\,$m$^3$, 1 yr', r"Phase IV$-$Ten full-size modules: $940\,$m$^3$, 8 yrs"], #150 MHz, $V = 94\,$m$^3$ + "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'One full-size module: $100\,$m$^3$, 1 yr', r"Phase IV$-$Ten full-size modules: $1000\,$m$^3$, 7 yrs"], #150 MHz, $V = 94\,$m$^3$ "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", From 1b505193a54dc4e0fa6cff87c02858be69d283a2 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 21 Nov 2025 18:52:18 -0500 Subject: [PATCH 257/262] Make code work when there's no comparison curve --- .../CavitySensitivityCurveProcessor.py | 41 +++++++++++-------- test_analysis/Cavity_Sensitivity_analysis.py | 19 +++++---- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index ffcd0385..8525dfbd 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -177,11 +177,12 @@ def InternalConfigure(self, params): self.sens_main_natoms_per_particle = 1 else: self.sens_main_natoms_per_particle = 2 - for i in range(len(self.sens_ref)): - if self.sens_ref_is_atomic[i]: - self.sens_ref[i].natoms_per_particle = 1 - else: - self.sens_ref[i].natoms_per_particle = 2 + if self.comparison_curve: + for i in range(len(self.sens_ref)): + if self.sens_ref_is_atomic[i]: + self.sens_ref[i].natoms_per_particle = 1 + else: + self.sens_ref[i].natoms_per_particle = 2 if self.atomic_axis: if self.sens_main_is_atomic: @@ -243,9 +244,10 @@ def InternalRun(self): #Optimizing the detection threshold for the comparison config files #Before the density optimization - for i in range(len(self.sens_ref)): - thresh_limits = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] - self.sens_ref[i].sens_with_configured_density_and_opt_thresh = np.min(thresh_limits) + if self.comparison_curve: + for i in range(len(self.sens_ref)): + thresh_limits = [self.sens_ref[i].CL90(Threshold={"detection_threshold": th}) for th in self.thresholds] + self.sens_ref[i].sens_with_configured_density_and_opt_thresh = np.min(thresh_limits) # create main plot @@ -271,8 +273,9 @@ def InternalRun(self): #Add point at configured density if self.density_axis and self.add_point_at_configured_density: self.ax.scatter([self.sens_main.Experiment.number_density*m**3], [self.sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.main_curve_color, label="Operating density", zorder=4) #label="Density: {:.{}f}".format(self.Experiment.number_density*m**3, 1) - for i in range(len(self.sens_ref)): - self.ax.scatter([self.sens_ref[i].Experiment.number_density*m**3], [self.sens_ref[i].sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.comparison_curve_colors[i], zorder=4) + if self.comparison_curve: + for i in range(len(self.sens_ref)): + self.ax.scatter([self.sens_ref[i].Experiment.number_density*m**3], [self.sens_ref[i].sens_with_configured_density_and_opt_thresh/eV], marker="s", s=25, color=self.comparison_curve_colors[i], zorder=4) # optimize density if self.optimize_main_density: @@ -709,10 +712,11 @@ def add_track_length_axis(self): ax2.set_xlim(self.sens_main.track_length(self.rhos[0])/s, self.sens_main.track_length(self.rhos[-1])/s) else: - for sens in self.sens_ref: - if sens.Experiment.atomic: - ax2.set_xlim(sens.track_length(self.rhos[0])/s, - sens.track_length(self.rhos[-1])/s) + if self.comparison_curve: + for sens in self.sens_ref: + if sens.Experiment.atomic: + ax2.set_xlim(sens.track_length(self.rhos[0])/s, + sens.track_length(self.rhos[-1])/s) if self.molecular_axis: ax3 = self.ax.twiny() @@ -731,10 +735,11 @@ def add_track_length_axis(self): ax3.set_xlim(self.sens_main.track_length(self.rhos[0])/s, self.sens_main.track_length(self.rhos[-1])/s) else: - for sens in self.sens_ref: - if not sens.Experiment.atomic: - ax3.set_xlim(sens.track_length(self.rhos[0])/s, - sens.track_length(self.rhos[-1])/s) + if self.comparison_curve: + for sens in self.sens_ref: + if not sens.Experiment.atomic: + ax3.set_xlim(sens.track_length(self.rhos[0])/s, + sens.track_length(self.rhos[-1])/s) if not self.molecular_axis and not self.atomic_axis: logger.warning("No track length axis added since neither atomic nor molecular was requested") diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 3b8d2845..7e1d8c69 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,8 +148,8 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./PhaseIV_sensitivity_vs_density_no-Bfield-res_fflat99pct_Nov-21-2025.pdf", #"./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -164,11 +164,12 @@ "y_limits": [2e-2, 6.5], "density_range": [3e14,3e18], #5e13 "det_thresh_range": [5, 115], + "add_point_at_configured_density": False, "main_curve_upper_label": r"LFA, threshold scenario", #560 MHz #Phase III scenario: 1 GHz", "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 - "comparison_curve": True, + "comparison_curve": False, "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", @@ -183,9 +184,9 @@ "label_x_position": 4e14, #4e14, #0.02, #1e14, "plot_key_parameters": True, } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { @@ -234,9 +235,9 @@ #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { From 53616b4dd3ee58a4742017e7a7ece7536dc32ffc Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Fri, 21 Nov 2025 18:57:24 -0500 Subject: [PATCH 258/262] Back to plots before testing effect of f_flat --- test_analysis/Cavity_Sensitivity_analysis.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 7e1d8c69..0a38ed59 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,8 +148,8 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg", #Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./PhaseIV_sensitivity_vs_density_no-Bfield-res_fflat99pct_Nov-21-2025.pdf", #"./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", + "config_file_path": "Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -164,12 +164,12 @@ "y_limits": [2e-2, 6.5], "density_range": [3e14,3e18], #5e13 "det_thresh_range": [5, 115], - "add_point_at_configured_density": False, + "add_point_at_configured_density": True, "main_curve_upper_label": r"LFA, threshold scenario", #560 MHz #Phase III scenario: 1 GHz", "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 - "comparison_curve": False, + "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", From 60049e52a18b6c3a6e183b3c8e2d4e30a7db6bb9 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Wed, 10 Dec 2025 12:43:51 -0500 Subject: [PATCH 259/262] For PIV, using unloaded Q values from Tianhuan's CST studies --- test_analysis/Cavity_Sensitivity_analysis.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index 0a38ed59..cefb3a01 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -148,8 +148,8 @@ # Configuration for Sensitivity vs. density plot sens_config_dict = { # required - "config_file_path": "Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Nov-21-2025.pdf", + "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Dec-10-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -184,15 +184,15 @@ "label_x_position": 4e14, #4e14, #0.02, #1e14, "plot_key_parameters": True, } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Nov-21-2025.pdf", #ncav-eff-time + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Dec-10-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -220,7 +220,7 @@ "goals_y_rel_position": {"LFA threshold (0.7 eV)": 0.83, "Phase IV (0.04 eV)": 0.83}, #6e14, #3.3e14, #5.5e13, "comparison_curve": True, "comparison_config_file_path": ["/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam.cfg", "/termite/sensitivity_config_files/Config_PIVmodule1_150MHz_minpitch_87deg.cfg", "/termite/sensitivity_config_files/Config_atomic_150MHz_minpitch_87deg.cfg"], #"/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", - "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'One full-size module: $100\,$m$^3$, 1 yr', r"Phase IV$-$Ten full-size modules: $1000\,$m$^3$, 7 yrs"], #150 MHz, $V = 94\,$m$^3$ + "comparison_curve_label": [r"LFA, target: $1.7\,$m$^3$, 1 yr", r'One full-size module: $100\,$m$^3$, 1 yr', r"Phase IV$-$Ten full-size modules: $1000\,$m$^3$, 8 yrs"], #150 MHz, $V = 94\,$m$^3$ "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", @@ -235,9 +235,9 @@ #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { From 938fbb5ea532c3718485ecf6251d27f1131e759f Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 16 Dec 2025 15:10:09 -0500 Subject: [PATCH 260/262] Add option to use arrays of carrier and sideband power fractions from sims. Still need trapped pitch dist for weighting. --- .../sensitivity/SensitivityCavityFormulas.py | 144 +++++++++++++----- test_analysis/Cavity_Sensitivity_analysis.py | 18 +-- 2 files changed, 115 insertions(+), 47 deletions(-) diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index a80be149..e7b880a7 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -35,9 +35,9 @@ def axial_motion(magnetic_field, pitch, trap_length, minimum_trapped_pitch, kin_ # also return the average magnetic field seen by the electron # from z=0 to z=cavity_length/2 with npoints set by the trajectory variable # See LUCKEY write-up for a little more on Talia's "flat fraction" trap model - - #pitch = pitch/180*np.pi - #minimum_trapped_pitch = minimum_trapped_pitch/180*np.pi + + # Input parameters: + # pitch and minimum_trapped_pitch are in radians # Axial motion: z_w = trap_length/2 @@ -133,6 +133,21 @@ def t_effective(t_physical, cyclotron_frequency): else: return quantum*(1/2+1/(np.exp(quantum/t_physical)-1)) + +# Calculate threshold z to trap electrons born at some pitch angle theta_start +# Electrons are trapped if they start a z values less than this threshold +# (Considering one axial side of the trap) +def max_z_to_trap_vs_theta_start(theta_start, trap_length, minimum_trapped_pitch, flat_fraction=0.5): + z_w = trap_length/2 + sec_min = 1/np.cos(minimum_trapped_pitch) + sin2_min = np.sin(minimum_trapped_pitch)**2 + sin2_start = np.sin(theta_start)**2 + return z_w*flat_fraction + z_w*(1-flat_fraction)*sec_min*np.sqrt(sin2_start - sin2_min) + + + + + # Trapping efficiency from axial field variation. def trapping_efficiency(z_range, bg_magnetic_field, min_pitch_angle, trap_flat_fraction = 0.5): @@ -239,6 +254,34 @@ def CalcDefaults(self, overwrite=False): self.bkgd_constant = 1 logger.info("Using background rate constant of 1/eV/s") + # Need to get power fractions before calculating effective volume (given impact on detection efficiency) + # Power fractions are relative to the power of a 90° carrier electron + # If average_power_fractions==True, use carrier and sideband pitch power fractions averaged over the usable pitch angle range. + # If average_power_fractions==False, instead read in a csv file with power fractions vs. pitch angle from simulations. + # This file should have three columns: pitch angle (in degrees), carrier power, sideband power. + if hasattr(self.FrequencyExtraction, "use_average_power_fractions"): + if self.FrequencyExtraction.use_average_power_fractions: + logger.info("Using average carrier and sideband power fractions") + else: + logger.info("Using carrier and sideband (power fractions vs. pitch angle) from file") + # Read powers from the file and then scale them by power of maximum + # pitch angle to get power fractions. + theta_array, carrier_power_array, sideband_power_array = [], [], [] + power_file = open(self.FrequencyExtraction.powers_vs_theta_file, 'r') + for i in power_file.readlines()[1:]: # Skip header line + line = i.strip() + theta_array.append(float(line.split(",")[0])) # In degrees + carrier_power_array.append(float(line.split(",")[1])) + sideband_power_array.append(float(line.split(",")[2])) + power_file.close() + self.theta_array = np.array(theta_array)*deg # The "*deg" multiplies by np.pi/180 + carrier_power_array = np.array(carrier_power_array) + sideband_power_array = np.array(sideband_power_array) + # The calculation below assumes that the file contains a pitch angle very close to 90 degrees: + max_theta_index = np.argmax(self.theta_array) + self.carrier_power_fraction_array = carrier_power_array / carrier_power_array[max_theta_index] + self.sideband_power_fraction_array = sideband_power_array / carrier_power_array[max_theta_index] + #Calculate the effective volume and print out related quantities self.EffectiveVolume() logger.info("Trap radius: {} cm".format(round(self.cavity_radius/cm, 3), 2)) @@ -256,7 +299,7 @@ def CalcDefaults(self, overwrite=False): self.CRLB_constant = self.FrequencyExtraction.crlb_constant logger.info("Using configured CRLB constant") - #Number of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation + # Number of steps in pitch angle between min_pitch and pi/2 for the frequency noise uncertainty calculation self.pitch_steps = 100 if hasattr(self.FrequencyExtraction, "pitch_steps"): self.pitch_steps = self.FrequencyExtraction.pitch_steps @@ -513,9 +556,12 @@ def syst_frequency_extraction(self): self.time_window_slope_zero = abs(self.cavity_freq-frequency(self.T_endpoint+20*meV, self.MagneticField.nominal_field))/self.slope - tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.carrier_power_fraction) - tau_snr_part_length = self.calculate_tau_snr(self.time_window_slope_zero, self.FrequencyExtraction.carrier_power_fraction) - + if self.FrequencyExtraction.use_average_power_fractions: + tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.carrier_power_fraction) + else: + tau_snr_full_length = self.calculate_tau_snr(self.time_window, self.carrier_power_fraction_array) + tau_snr_full_length = tau_snr_full_length[:len(self.theta_array)-1] #Cut out theta=pi/2, since sideband power is 0 there, resulting in infinite tau_snr. + #Calculate the frequency variance from the CRLB self.var_f_c_CRLB = self.frequency_variance_from_CRLB(tau_snr_full_length) self.best_time_window = self.time_window @@ -525,16 +571,29 @@ def syst_frequency_extraction(self): #Calculate noise contribution to uncertainty, including energy correction for pitch angle. #This comes from section 6.1.9 of the CDR. - tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) + if self.FrequencyExtraction.use_average_power_fractions: + tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.FrequencyExtraction.sideband_power_fraction) + else: + tau_snr_full_length_sideband = self.calculate_tau_snr(self.time_window, self.sideband_power_fraction_array) + tau_snr_full_length_sideband = tau_snr_full_length_sideband[:len(self.theta_array)-1] #Cut out theta=pi/2, since sideband power is 0 there, resulting in infinite tau_snr. + # (sigmaf_lsb)^2: var_f_sideband_crlb = self.frequency_variance_from_CRLB(tau_snr_full_length_sideband) #var_f_sideband_crlb = self.FrequencyExtraction.CRLB_scaling_factor*(self.CRLB_constant*tau_snr_full_length_sideband/self.time_window**3)/(2*np.pi)**2 m = self.FrequencyExtraction.sideband_order #For convenience - #Define phi_max, corresponding to the minimum pitch angle - phi_max = np.pi/2 - self.FrequencyExtraction.minimum_angle_in_bandwidth - phis = np.linspace(0, phi_max, self.pitch_steps) + # Defining array of pitch angle complements (pi/2 - theta) used when calculating + # the parameters describing the track shape (p and q) + thetas_for_p_and_q_calc = np.linspace(self.FrequencyExtraction.minimum_angle_in_bandwidth, 90*deg, self.pitch_steps) + pitch_comps_for_p_and_q_calc = np.pi/2 - thetas_for_p_and_q_calc + + # Defining array of pitch angle complement values over which we calculate the + # resolution contribution from noise. + if self.FrequencyExtraction.use_average_power_fractions: + pitch_comps = pitch_comps_for_p_and_q_calc + else: + pitch_comps = np.pi/2 - self.theta_array[:len(self.theta_array)-1] #Cut out theta=pi/2 since sideband power is 0 there, resulting in infinite tau_snr. #Define the trap parameter p based on the relation between the trap length and the cavity mode #This p is for a box trap @@ -543,44 +602,47 @@ def syst_frequency_extraction(self): #Now find p for the actual trap that we have #Using the average p across the pitch angle range ax_freq_array, mean_field_array, z_t = axial_motion(self.MagneticField.nominal_field, - np.pi/2-phis, self.Experiment.trap_length, + thetas_for_p_and_q_calc, self.Experiment.trap_length, self.FrequencyExtraction.minimum_angle_in_bandwidth, self.T_endpoint, flat_fraction=self.MagneticField.trap_flat_fraction) fc0_endpoint = self.cavity_freq - p_array = ax_freq_array/fc0_endpoint/phis - self.p = np.mean(p_array[1:]) #Cut out theta=pi/2 (ill defined there) + p_array = ax_freq_array/fc0_endpoint/pitch_comps_for_p_and_q_calc #An array + if self.FrequencyExtraction.use_average_power_fractions: + p_array = p_array[:1] #Cut out theta=pi/2 (ill defined there) + self.p = np.mean(p_array) - #Now calculating q for the trap that we have - #Using the q for the minimum trapped pitch angle - fc_endpoint_min_theta = frequency(self.T_endpoint, mean_field_array[self.pitch_steps-1]) - self.q = (fc_endpoint_min_theta/fc0_endpoint - 1)/(phis[self.pitch_steps-1])**2 + # Now calculating q for the trap that we have + # Using the q for the minimum trapped pitch angle + fc_endpoint_min_theta = frequency(self.T_endpoint, mean_field_array[0]) + self.q = (fc_endpoint_min_theta/fc0_endpoint - 1)/(pitch_comps_for_p_and_q_calc[0])**2 - #Derivative of f_c0 (frequency corrected to B-field at bottom of the trap) with respect to f_c - dfc0_dfc_array = 0.5*(1 - (1 - 4*self.q*phis/m/self.p + self.q*phis**2)/(1 - self.q*phis**2)) + # Derivative of f_c0 (frequency corrected to B-field at bottom of the trap) with respect to f_c + dfc0_dfc_array = 0.5*(1 - (1 - 4*self.q*pitch_comps/m/self.p + self.q*pitch_comps**2)/(1 - self.q*pitch_comps**2)) - #Derivative of f_c0 with respect to f_lsb (lower sideband frequency) - dfc0_dlsb_array = 0.5 - 2*self.q*phis/m/self.p/(1 - self.q*phis**2) + # Derivative of f_c0 with respect to f_lsb (lower sideband frequency) + dfc0_dlsb_array = 0.5 - 2*self.q*pitch_comps/m/self.p/(1 - self.q*pitch_comps**2) - #Noise variance term from the carrier frequency uncertainty + # Noise variance term from the carrier frequency uncertainty var_noise_from_fc_array = dfc0_dfc_array**2*self.var_f_c_CRLB - #Noise variance term from the lower sideband frequency uncertainty + # Noise variance term from the lower sideband frequency uncertainty var_noise_from_flsb_array = dfc0_dlsb_array**2*var_f_sideband_crlb - #Total uncertainty for each pitch angle + # Total uncertainty for each pitch angle var_f_noise_array = var_noise_from_fc_array + var_noise_from_flsb_array - #Next, we average over sigma_noise values. - #This is a quadrature sum average, - #reflecting that the detector response function could be constructed by sampling - #from many normal distributions with different standard deviations (sigma_noise_array), - #then finding the standard deviation of the full group of sampled values. + # Next, we average over sigma_noise values. + # This is a quadrature sum average, + # reflecting that the detector response function could be constructed by sampling + # from many normal distributions with different standard deviations (sigma_noise_array), + # then finding the standard deviation of the full group of sampled values. + # CHANGE STILL NEEDED: AVERAGE OVER THE TRAPPED PITCH ANGLE DISTRIBUTION self.sigma_f_noise = np.sqrt(np.sum(var_f_noise_array)/self.pitch_steps) else: self.sigma_f_noise = np.sqrt(self.var_f_c_CRLB) - #Convert uncertainty from frequency to energy + # Convert uncertainty from frequency to energy self.sigma_K_noise = e*self.MagneticField.nominal_field/(2*np.pi*endpoint_frequency**2)*self.sigma_f_noise*c0**2 # combined sigma_f in eV @@ -675,7 +737,10 @@ def det_efficiency_track_duration(self): # Calculate the mean track duration #FIX: Only do the lines below ones for a given density; don't repeat for each threshold being scanned ... mean_track_duration = track_length(self.Experiment.number_density, self.T_endpoint, molecular=(not self.Experiment.atomic)) - tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction + self.FrequencyExtraction.sideband_power_fraction, tau_snr_array_for_radii=self.Efficiency.calculate_det_eff_for_sampled_radii) + if self.FrequencyExtraction.use_average_power_fractions: + tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.FrequencyExtraction.carrier_power_fraction + self.FrequencyExtraction.sideband_power_fraction, tau_snr_array_for_radii=self.Efficiency.calculate_det_eff_for_sampled_radii) + else: + tau_snr_ex_total = self.calculate_tau_snr(mean_track_duration, self.carrier_power_fraction_array + self.sideband_power_fraction_array, tau_snr_array_for_radii=self.Efficiency.calculate_det_eff_for_sampled_radii) if isinstance(tau_snr_ex_total, float): tau_snr_ex_total = [tau_snr_ex_total] @@ -689,10 +754,11 @@ def det_efficiency_track_duration(self): sf_values = np.array([ncx2(df=2, nc=2 * scaled_x / tau_snr).sf(self.Threshold.detection_threshold) for tau_snr in tau_snr_ex_total]) # Calculate and return the integration result from weighted sum - eff_for_each_r = np.sum(w * sf_values, axis=1) + eff_for_each_r_and_theta = np.sum(w * sf_values, axis=1) - #Average efficiencies over the sampled electron radii. Weighting for radial distribution is accounted for in sampling, earlier. - avg_efficiency = np.mean(eff_for_each_r) + #Average efficiencies over the sampled electron radii and pitch angles. Weighting for radial distribution is accounted for in sampling, earlier. + #WEIGHTING FOR PITCH ANGLE DISTRIBUTION NOT YET INCLUDED. + avg_efficiency = np.mean(eff_for_each_r_and_theta) return avg_efficiency def assign_detection_efficiency_from_threshold(self): @@ -727,9 +793,11 @@ def print_SNRs(self, rho=None): tau_snr_90deg = self.calculate_tau_snr(track_duration, power_fraction=1) #For an example carrier: - tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) - - + if self.FrequencyExtraction.use_average_power_fractions: + tau_snr_ex_carrier = self.calculate_tau_snr(track_duration, self.FrequencyExtraction.carrier_power_fraction) + else: + tau_snr_ex_carrier = np.mean(self.calculate_tau_snr(track_duration, self.carrier_power_fraction_array)) + eV_bandwidth = np.abs(self.cavity_freq - frequency(self.T_endpoint + 1*eV, self.MagneticField.nominal_field)) SNR_1eV_90deg = 1/eV_bandwidth/tau_snr_90deg SNR_track_duration_90deg = track_duration/tau_snr_90deg diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index cefb3a01..a445c5d5 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Dec-10-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Dec-16-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -169,7 +169,7 @@ "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 - "comparison_curve": True, + "comparison_curve": False, "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", @@ -184,15 +184,15 @@ "label_x_position": 4e14, #4e14, #0.02, #1e14, "plot_key_parameters": True, } -#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -#sens_curve.Configure(sens_config_dict) -#sens_curve.Run() +sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +sens_curve.Configure(sens_config_dict) +sens_curve.Run() sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Dec-10-2025.pdf", #ncav-eff-time + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Dec-16-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6), @@ -235,9 +235,9 @@ #"goals_x_position": 0.12, #4e-2, #<-- Number for ncav*eff*time #0.11, <-- Number for ncavities*livetime #"goals_y_rel_position": 0.86, #0.84, <-- Number for ncav*eff*time #0.81, <-- Number for ncavities*livetime } -sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") -sens_curve.Configure(sens_config_dict) -sens_curve.Run() +#sens_curve = CavitySensitivityCurveProcessor("sensitivity_curve_processor") +#sens_curve.Configure(sens_config_dict) +#sens_curve.Run() sens_config_dict = { From 45d56f7742f5cf3e7e0dae93069f74733d12f0e2 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Tue, 16 Dec 2025 15:18:59 -0500 Subject: [PATCH 261/262] New sens vs density with all 4 curves --- test_analysis/Cavity_Sensitivity_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index a445c5d5..a6c156f4 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -169,7 +169,7 @@ "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 - "comparison_curve": False, + "comparison_curve": True, "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", From 8d5de259ec7472fff3ad65ae78256debc0e1be18 Mon Sep 17 00:00:00 2001 From: taliaweiss Date: Thu, 18 Dec 2025 17:43:23 -0500 Subject: [PATCH 262/262] Added trapped pitch angle dist calc; reduced output for verbose==False --- .../CavitySensitivityCurveProcessor.py | 6 +- .../sensitivity/SensitivityCavityFormulas.py | 60 +++++++++++++++++-- mermithid/sensitivity/SensitivityFormulas.py | 13 ++-- test_analysis/Cavity_Sensitivity_analysis.py | 7 ++- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py index 8525dfbd..fe76187a 100644 --- a/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py +++ b/mermithid/processors/Sensitivity/CavitySensitivityCurveProcessor.py @@ -141,16 +141,16 @@ def InternalConfigure(self, params): # setup sensitivities if self.add_PhaseII: - self.sens_PhaseII = CavitySensitivity(self.PhaseII_path) + self.sens_PhaseII = CavitySensitivity(self.PhaseII_path, verbose=self.verbose) - self.sens_main = CavitySensitivity(self.config_file_path) + self.sens_main = CavitySensitivity(self.config_file_path, verbose=self.verbose) self.sens_main_is_atomic = self.sens_main.Experiment.atomic if self.comparison_curve: ref = [] for file in self.comparison_config_file_path: - ref.append(CavitySensitivity(file)) + ref.append(CavitySensitivity(file, verbose=self.verbose)) self.sens_ref = ref is_atomic = [] diff --git a/mermithid/sensitivity/SensitivityCavityFormulas.py b/mermithid/sensitivity/SensitivityCavityFormulas.py index e7b880a7..8c102125 100755 --- a/mermithid/sensitivity/SensitivityCavityFormulas.py +++ b/mermithid/sensitivity/SensitivityCavityFormulas.py @@ -135,7 +135,7 @@ def t_effective(t_physical, cyclotron_frequency): # Calculate threshold z to trap electrons born at some pitch angle theta_start -# Electrons are trapped if they start a z values less than this threshold +# Electrons are trapped if they start at z values less than this threshold # (Considering one axial side of the trap) def max_z_to_trap_vs_theta_start(theta_start, trap_length, minimum_trapped_pitch, flat_fraction=0.5): z_w = trap_length/2 @@ -144,8 +144,58 @@ def max_z_to_trap_vs_theta_start(theta_start, trap_length, minimum_trapped_pitch sin2_start = np.sin(theta_start)**2 return z_w*flat_fraction + z_w*(1-flat_fraction)*sec_min*np.sqrt(sin2_start - sin2_min) - - +def dist_of_theta_start_after_trapping(theta_start, trap_length, minimum_trapped_pitch, flat_fraction=0.5): + # Distribution of theta_start for electrons born uniformly along z + # Multiplied by sin(theta_start), to account for the birth pitch angles - is this + # correct? Is another normalization needed after multiplying by sin(theta_start)? + z_threshold = max_z_to_trap_vs_theta_start(theta_start, trap_length, minimum_trapped_pitch, flat_fraction) + return z_threshold/(trap_length/2)*np.sin(theta_start) + +def theta_bottom_from_theta_start(theta_start, B_min, B_start): + return np.arcsin(np.sin(theta_start)*np.sqrt(B_min/B_start)) + +def dist_of_theta_bottom_after_trapping(B_min, theta_start_array, trap_length, minimum_trapped_pitch, flat_fraction=0.5, n_z_start=100, n_theta_bottom=10): + z_start_array = np.linspace(0, trap_length/2, n_z_start) + B_start_array = magnetic_field_flat_harmonic(z_start_array, B_min, trap_length, minimum_trapped_pitch, flat_fraction) + theta_bottoms = [] + for theta_start in theta_start_array: + theta_bottoms.append(theta_bottom_from_theta_start(theta_start, B_min, B_start_array)) + theta_bottoms = np.array(theta_bottoms) + theta_bottoms_bin_centers = np.linspace(minimum_trapped_pitch, np.pi/2, n_theta_bottom) + bin_size = (np.pi/2 - minimum_trapped_pitch)/n_theta_bottom + prob_theta_bottom = np.zeros(len(theta_bottoms_bin_centers)) + for i in range(len(theta_bottoms)): + for j in range(len(theta_bottoms[0])): + for k in range(len(theta_bottoms_bin_centers)): + if (theta_bottoms[i][j] >= theta_bottoms_bin_centers[k]-bin_size/2) and (theta_bottoms[i][j] < theta_bottoms_bin_centers[k]+bin_size/2): + prob_theta_bottom[k] += dist_of_theta_start_after_trapping(theta_start_array[i], trap_length, minimum_trapped_pitch, flat_fraction) + normalization = np.sum(prob_theta_bottom) + prob_theta_bottom = prob_theta_bottom/normalization #Is this the correct approach? + return theta_bottoms_bin_centers, prob_theta_bottom + +# Make the plots below by default? Set up for plots to only be created once when +# running CavitySensitivityCurveProcessor? +""" +import matplotlib.pyplot as plt +figure = plt.figure() +theta_start_array = np.linspace(87*deg, np.pi/2, 1000) +prob_theta_start = dist_of_theta_start_after_trapping(theta_start_array, 4.05*m, 87*deg, flat_fraction=0.75) +plt.scatter(theta_start_array/deg, prob_theta_start) +plt.xlabel("Starting pitch angle $\\theta_{start}$ ($\degree$)", fontsize=14) +plt.ylabel("Probability (arb. units)", fontsize=14) +plt.savefig("test_theta_start_dist.png", dpi=300) +plt.show() + +theta_bottoms_bin_centers, prob_theta_bottom = dist_of_theta_bottom_after_trapping(21*mT, theta_start_array, 4.05*m, 87*deg, flat_fraction=0.75, n_z_start=1000, n_theta_bottom=50) +#print("theta bottoms", theta_bottoms_bin_centers/deg) +#print("prob theta bottom", prob_theta_bottom) +figure = plt.figure() +plt.scatter(theta_bottoms_bin_centers/deg, prob_theta_bottom, s=1) +plt.xlabel("Pitch angle at bottom of trap $\\theta_{bottom}$ ($\degree$)", fontsize=14) +plt.ylabel("Probability density", fontsize=14) +plt.savefig("test_theta_bottom_dist.png", dpi=300) +plt.show() +""" # Trapping efficiency from axial field variation. @@ -207,8 +257,8 @@ class CavitySensitivity(Sensitivity): * Nick's CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ - def __init__(self, config_path): - Sensitivity.__init__(self, config_path) + def __init__(self, config_path, verbose=True): + Sensitivity.__init__(self, config_path, verbose=verbose) # Calc non-config parameters outside of init function: ## Allows re-calcing params if config values changed later, e.g. param scans diff --git a/mermithid/sensitivity/SensitivityFormulas.py b/mermithid/sensitivity/SensitivityFormulas.py index 7b3e15cc..46187b75 100755 --- a/mermithid/sensitivity/SensitivityFormulas.py +++ b/mermithid/sensitivity/SensitivityFormulas.py @@ -37,18 +37,19 @@ class Sensitivity(object): * Nicks CRLB for frequency resolution: https://3.basecamp.com/3700981/buckets/3107037/uploads/2009854398 * Molecular contamination in atomic tritium: https://3.basecamp.com/3700981/buckets/3107037/documents/3151077016 """ - def __init__(self, config_path): + def __init__(self, config_path, verbose=True): self.cfg = configparser.ConfigParser() with open(config_path, 'r') as configfile: self.cfg.read_file(configfile) # display configuration try: - logger.info("Config file content:") - for sect in self.cfg.sections(): - logger.info(' Section: {}'.format(sect)) - for k,v in self.cfg.items(sect): - logger.info(' {} = {}'.format(k,v)) + if verbose: + logger.info("Config file content:") + for sect in self.cfg.sections(): + logger.info(' Section: {}'.format(sect)) + for k,v in self.cfg.items(sect): + logger.info(' {} = {}'.format(k,v)) except: pass diff --git a/test_analysis/Cavity_Sensitivity_analysis.py b/test_analysis/Cavity_Sensitivity_analysis.py index a6c156f4..11503d0c 100644 --- a/test_analysis/Cavity_Sensitivity_analysis.py +++ b/test_analysis/Cavity_Sensitivity_analysis.py @@ -149,7 +149,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment_1GHz.cfg", #Config_atomic_325MHz_Experiment_conservative.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Dec-16-2025.pdf", + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_density_target_and_threshold_Dec-18-2025.pdf", # optional "figsize": (7.5,6.4), "fontsize": 15, @@ -169,7 +169,8 @@ "goals": {"LFA threshold (0.7 eV)": 0.7, "Phase IV (0.04 eV)": 0.04}, #"Pilot T goal (0.1 eV)": 0.1, "goals_x_position": {"LFA threshold (0.7 eV)": 2.6e16, "Phase IV (0.04 eV)": 4e14}, #6e14, #3.3e14, #5.5e13, "goals_y_rel_position": {"LFA threshold (0.7 eV)": 1.1, "Phase IV (0.04 eV)": 0.79}, #0.755 - "comparison_curve": True, + "comparison_curve": False, + "verbose": False, "main_curve_color": "blue", "comparison_curve_colors": ["blue", "darkred", "black"], "main_curve_linestyle": "dashed", @@ -192,7 +193,7 @@ sens_config_dict = { # required "config_file_path": "/termite/sensitivity_config_files/Config_LFA_Experiment_max_BNL_diam_threshold.cfg", #"/termite/sensitivity_config_files/Config_LFA_Experiment.cfg", - "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Dec-16-2025.pdf", #ncav-eff-time + "plot_path": "./LFA_and_PhaseIV_sensitivity_vs_livetime_curve_target_and_threshold_Dec-18-2025.pdf", #ncav-eff-time "exposure_axis": True, # optional "figsize": (8.3, 6.3), #(10,6),