class VariableHolder(ParamHolder): """ Allows parameter values to be interpolated using a frequency repsonse file""" fname = Property(dtype=str, default=None, help="Data file") var_type = Choice(choices=['pdf', 'dist', 'gauss', 'const'], default='const') def __init__(self, *args, **kwargs): """ Constuctor """ self._value = None super(VariableHolder, self).__init__(*args, **kwargs) self._sampled_values = None self._cached_interps = None @staticmethod def _channel_value(arr, chan_idx): """ Picks the value for a paricular channel, This allows using a single parameter to represent multiple channels """ if not isinstance(arr, Iterable): return arr if not arr.shape: return arr if len(arr) < 2: return arr[0] return arr[chan_idx] def _cache_interps(self, freqs=None): """ Cache the interpolator object """ if self.var_type == 'const': self._cached_interps = None return if self.var_type == 'gauss': if np.isnan(self.errors).any(): self._cached_interps = None return self._cached_interps = np.array([ sps.norm(loc=val_, scale=sca_) for val_, sca_ in zip( np.atleast_1d(self.value), np.atleast_1d(self.errors)) ]) return tokens = self.fname.split(',') if self.var_type == 'pdf': self._cached_interps = np.array( [ChoiceDist(cfg_path(token)) for token in tokens]) self._value = np.array( [pdf.mean() for pdf in self._cached_interps]) return self._cached_interps = np.array( [FreqInterp(cfg_path(token)) for token in tokens]) if freqs is None: self._value = np.array( [pdf.mean_trans() for pdf in self._cached_interps]) return self._value = np.array( [pdf.rvs(freqs) for pdf in self._cached_interps]) def rvs(self, nsamples, freqs=None, chan_idx=0): """ Sample values This just returns the sampled values. It does not store them. """ self._cache_interps(freqs) val = self._channel_value(self.value, chan_idx) if self._cached_interps is None or not nsamples: return val interp = self._channel_value(self._cached_interps, chan_idx) if self.var_type == 'gauss': return interp.rvs(nsamples).reshape((nsamples, 1)) if self.var_type == 'pdf': return interp.rvs(nsamples).reshape((nsamples, 1)) return interp.rvs(freqs, nsamples).reshape((nsamples, len(freqs))) def sample(self, nsamples, freqs=None, chan_idx=0): """ Sample values This stores the sampled values. """ self._sampled_values = self.rvs(nsamples, freqs, chan_idx) return self.SI def unsample(self): """ This removes the stored sampled values""" self._sampled_values = None @property def scaled(self): """Return the product of the value and the scale This uses the stored sampled values if they are present. """ if self._sampled_values is not None: return self._sampled_values * self.scale return super(VariableHolder, self).scaled
class TestClass(Model): v = Property(dtype=dtype, default=default, help="A Property") v2 = Property(dtype=dtype, help="A Property") v3 = Property(dtype=dtype, required=True, help="A Property") v4 = Property(dtype=dtype, default=test_val, help="A Property")
class Atmosphere(Model): """ Atmosphere model """ atm_model_file = Property(dtype=str) def __init__(self, **kwargs): """ Constructor """ self._atm_model = None self._telescope = None self._sampled_keys = None self._nsamples = 1 super(Atmosphere, self).__init__(**kwargs) def set_telescope(self, value): """ Set the telescope This is needed to sample elevation and PWV values """ self._telescope = value @cached(uses=[atm_model_file]) def cached_model(self): """ Cache the Atmosphere model """ if is_not_none(self._telescope.custom_atm_file): return CustomAtm(cfg_path(self._telescope.custom_atm_file)) if is_not_none(self.atm_model_file): return AtmModel(cfg_path(self.atm_model_file), self._telescope.site) return None def sample(self, nsamples): """ Sample the atmosphere """ model = self.cached_model if isinstance(model, CustomAtm): self._sampled_keys = None self._nsamples = nsamples return self._telescope.pwv.sample(nsamples) self._telescope.elevation.sample(nsamples) self._sampled_keys = model.get_keys( 1e-6 * np.atleast_1d(self._telescope.pwv()), np.atleast_1d(self._telescope.elevation())) #pylint: disable=no-member self._nsamples = max(nsamples, 1) def temp(self, freqs): """ Get sampled temperatures """ model = self.cached_model nfreqs = len(freqs) out_shape = (max(self._nsamples, 1), 1, nfreqs) if self._sampled_keys is None: ones = np.ones((max(self._nsamples, 1), 1, 1)) return (ones * model.temp(freqs)).reshape(out_shape) #pylint: disable=no-member #out_shape = (1, 1, nfreqs) #return model.temp(freqs).reshape(out_shape) #pylint: disable=no-member return model.temp(self._sampled_keys, freqs).reshape(out_shape) #pylint: disable=no-member def trans(self, freqs): """ Get sampled transmission coefs """ model = self.cached_model nfreqs = len(freqs) out_shape = (max(self._nsamples, 1), 1, nfreqs) if self._sampled_keys is None: ones = np.ones((max(self._nsamples, 1), 1, 1)) return (ones * model.trans(freqs)).reshape(out_shape) #pylint: disable=no-member #out_shape = (1, 1, nfreqs) #return model.trans(freqs).reshape(out_shape) #pylint: disable=no-member return model.trans(self._sampled_keys, freqs).reshape(out_shape) #pylint: disable=no-member
class TestClass(Model): vv = Property()
class TestClass(Model): vv = Property(dummy=3)
class OtherClass(Model): a = Property(dtype=float, default=3., help='variable a') x = Ref() z = Ref(attr='z') der = Ref(attr='der')
class Channel(Model): #pylint: disable=too-many-instance-attributes """ Channel Model """ _min_tc_tb_diff = 0.010 band_center = Variable(unit="GHz") fractional_bandwidth = Variable(default=.35) band_response = Variable(default=1.) det_eff = Variable(default=1.) squid_nei = Variable(default=1., unit='pA/rtHz') bolo_resistance = Variable(default=1., unit='Ohm') pixel_size = Variable(default=6.8, unit='mm') waist_factor = Variable(default=3.) Tc = Variable(default=.165, unit='K') Tc_fraction = Variable() num_det_per_water = Property(dtype=int, default=542) num_wafer_per_optics_tube = Property(dtype=int, default=1) num_optics_tube = Property(dtype=int, default=3) psat = Variable() psat_factor = Variable(default=3.) read_frac = Variable(default=0.1) carrier_index = Variable(default=3) G = Variable(unit='pW/K') Flink = Variable() Yield = Variable() response_factor = Variable() nyquist_inductance = Variable() noise_calc = noise.Noise() def __init__(self, **kwargs): """ Constructor """ self._optical_effic = None self._optical_emiss = None self._optical_temps = None self._sky_temp_dict = None self._sky_tran_dict = None self._det_effic = None self._det_emiss = None self._det_temp = None self._camera = None self._idx = None super(Channel, self).__init__(**kwargs) self._freqs = None self._flo = None self._fhi = None self._freq_mask = None self.bandwidth = None def set_camera(self, camera, idx): """ Set the parent camera and the channel index """ self._camera = camera self._idx = idx def sample(self, nsamples): """ Sample PDF parameters """ self.det_eff.sample(nsamples) self.squid_nei.sample(nsamples) self.bolo_resistance.sample(nsamples) @property def camera(self): """ Return the parent camera """ return self._camera @property def freqs(self): """ Return the evaluation frequencies """ return self._freqs @property def flo(self): """ Return the -3dB point""" return self._flo @property def fhi(self): """ Return the +3dB point """ return self._fhi @property def ndet(self): """ Return the total number of detectors per channel """ return self.num_det_per_water * self.num_wafer_per_optics_tube * self.num_optics_tube @property def idx(self): """ Return the channel index """ return self._idx def photon_NEP(self, elem_power, elems=None, ap_names=None): """ Return the photon NEP given the power in the element in the optical chain """ if elems is None: return self.noise_calc.photon_NEP(elem_power, self._freqs) det_pitch = self.pixel_size.SI / (self.camera.f_number.SI * physics.lamb(self.band_center.SI)) return self.noise_calc.photon_NEP(elem_power, self._freqs, elems=elems, det_pitch=det_pitch, ap_names=ap_names) def bolo_Psat(self, opt_pow): """ Return the PSAT used the the computation """ if is_not_none(self.psat_factor) and np.isfinite(self.psat_factor.SI): p_sat = opt_pow * self.psat_factor.SI else: p_sat = self.psat.SI return p_sat def bolo_G(self, opt_pow): """ Return the Bolometeric G factor used in the computation """ tb = self._camera.bath_temperature() tc = self.Tc.SI n = self.carrier_index.SI if is_not_none(self.G) and np.isfinite(self.G.SI).all(): g = self.G.SI else: if is_not_none(self.psat_factor) and np.isfinite( self.psat_factor.SI): p_sat = opt_pow * self.psat_factor.SI else: p_sat = self.psat.SI g = noise.G(p_sat, n, tb, tc) return g def bolo_Flink(self): """ Return the Bolometeric f-link used in the computation """ tb = self._camera.bath_temperature() tc = self.Tc.SI n = self.carrier_index.SI if is_not_none(self.Flink) and np.isfinite(self.Flink.SI): flink = self.Flink.SI else: flink = noise.Flink(n, tb, tc) return flink def bolo_NEP(self, opt_pow): """ Return the bolometric NEP given the detector details """ tb = self._camera.bath_temperature() tc = self.Tc.SI n = self.carrier_index.SI #1. #self.n if is_not_none(self.G) and np.isfinite(self.G.SI).all(): g = self.G.SI else: if is_not_none(self.psat_factor) and np.isfinite( self.psat_factor.SI): p_sat = opt_pow * self.psat_factor.SI else: p_sat = self.psat.SI g = noise.G(p_sat, n, tb, tc) if is_not_none(self.Flink) and np.isfinite(self.Flink.SI): flink = self.Flink.SI else: flink = noise.Flink(n, tb, tc) return noise.bolo_NEP(flink, g, tc) def read_NEP(self, opt_pow): """ Return the readout NEP given the detector details """ if np.isnan(self.squid_nei.SI).any(): return None if np.isnan(self.bolo_resistance.SI).any(): return None if is_not_none(self.psat) and np.isfinite(self.psat.SI).all(): p_stat = self.psat.SI else: p_stat = self.psat_factor.SI * opt_pow p_bias = (p_stat - opt_pow).clip(0, np.inf) if is_not_none(self.response_factor) and np.isfinite( self.response_factor.SI).all(): s_fact = self.response_factor.SI else: s_fact = 1. return noise.read_NEP(p_bias, self.bolo_resistance.SI.T, self.squid_nei.SI.T, s_fact) def compute_evaluation_freqs(self, freq_resol=None): """ Compute and return the evaluation frequencies """ self.bandwidth = self.band_center.SI * self.fractional_bandwidth.SI if freq_resol is None: freq_resol = 0.05 * self.bandwidth else: freq_resol = freq_resol * 1e9 self._flo = self.band_center.SI - 0.5 * self.bandwidth self._fhi = self.band_center.SI + 0.5 * self.bandwidth freq_step = np.ceil(self.bandwidth / freq_resol).astype(int) self._freqs = np.linspace(self._flo, self._fhi, freq_step + 1) band_mean_response = self.band_response.sample(0, self._freqs) if np.isscalar(band_mean_response): return self._freqs self._flo, self._fhi = physics.band_edges(self._freqs, band_mean_response) self.bandwidth = self._fhi - self._flo freq_mask = np.bitwise_and(self._freqs >= self._flo, self._freqs <= self._fhi) self._freqs = self._freqs[freq_mask] return self._freqs def eval_optical_chain(self, nsample=0, freq_resol=None): """ Evaluate the performance of the optical chain for this channel """ self.compute_evaluation_freqs(freq_resol) self._optical_effic = [] self._optical_emiss = [] self._optical_temps = [] for elem in self._camera.optics.values(): effic, emiss, temps = elem.compute_channel(self, self._freqs, nsample) self._optical_effic.append(effic) self._optical_emiss.append(emiss) self._optical_temps.append(temps) def eval_det_response(self, nsample=0, freq_resol=None): """ Evaluate the detector response for this channel """ self._freqs = self.compute_evaluation_freqs(freq_resol) self.band_response.sample(nsample, self._freqs) self.det_eff.sample(nsample) #def_eff_shaped = np.expand_dims(self.det_eff(), -1) self._det_effic = self.band_response.SI * self.det_eff.SI self._det_emiss = 0. self._det_temp = self._camera.bath_temperature() def eval_sky(self, universe, freq_resol=None): """ Evaluate the sky parameters for this channel This is done here, b/c the frequencies we care about are chanel dependent. """ self._freqs = self.compute_evaluation_freqs(freq_resol) self._sky_temp_dict = universe.temp(self._freqs) self._sky_tran_dict = universe.trans(self._freqs) @property def optical_effic(self): """ Return the optical element efficiecies for this channel """ return self._optical_effic @property def optical_emiss(self): """ Return the optical element emissivities for this channel """ return self._optical_emiss @property def optical_temps(self): """ Return the optical element temperatures for this channel """ return self._optical_temps @property def sky_names(self): """ Return the list of the names of the sky components """ return list(self._sky_temp_dict.keys()) @property def sky_temps(self): """ Return the sky component temperatures for this channel """ return [self._sky_temp_dict.get(k) for k in Universe.sources] @property def sky_effic(self): """ Return the sky component efficiecies for this channel """ return [self._sky_tran_dict.get(k, 1.) for k in Universe.sources] @property def sky_emiss(self): """ Return the sky component emissivities for this channel """ return [1] * len(Universe.sources)
class Instrument(Model): """ Class to represent an instrument """ site = Property(dtype=str, required=True) sky_temp = Variable(required=True, unit='K') obs_time = Variable(required=True, unit='yr') sky_fraction = Variable(required=True) NET = Variable(required=True) custom_atm_file = Property(dtype=str) elevation = Variable(required=True) pwv = Variable(required=True) obs_effic = Variable(required=True) readout = Property(dtype=Readout, required=True) camera_config = Property(dtype=dict, required=True) optics_config = Property(dtype=dict, required=True) channel_default = Property(dtype=dict, required=True) def __init__(self, **kwargs): """ Constructor """ super(Instrument, self).__init__(**kwargs) self.optics = build_optics_class(**self.optics_config) self.cameras = build_cameras(self.channel_default, self.camera_config) self._tables = None self._sns_dict = None for key, val in self.cameras.items(): self.__dict__[key] = val val.set_parent(self, key) def eval_sky(self, universe, nsamples=0, freq_resol=None): """ Sample requested inputs and evaluate the parameters of the sky model """ universe.sample(nsamples) self.obs_effic.sample(nsamples) for camera in self.cameras.values(): camera.eval_sky(universe, freq_resol) def eval_instrument(self, nsamples=0, freq_resol=None): """ Sample requested inputs and evaluate the parameters of the instrument model """ for camera in self.cameras.values(): camera.sample(nsamples) camera.eval_optical_chains(nsamples, freq_resol) camera.eval_det_response(nsamples, freq_resol) def eval_sensitivities(self): """ Evaluate the sensitvities """ self._sns_dict = odict() for cam_name, camera in self.cameras.items(): for chan_name, channel in camera.channels.items(): full_name = "%s%s" % (cam_name, chan_name) self._sns_dict[full_name] = Sensitivity(channel) def make_tables(self, basename="", **kwargs): """ Make fits tables with output values """ self._tables = TableDict() for key, val in self._sns_dict.items(): val.make_tables("%s%s" % (basename, key), self._tables, **kwargs) # get the summary tables and stack them if kwargs.get('save_summary', True): sum_keys = [ key for key in self._tables.keys() if key.find('_summary') > 0 ] sum_table = vstack([self._tables.pop_table(sum_key) for sum_key in sum_keys]) self._tables.add_datatable("%ssummary" % basename, sum_table) # get the optical tables adn stack them if kwargs.get('save_optical', True): opt_keys = [ key for key in self._tables.keys() if key.find('_optical') > 0 ] opt_table = vstack([self._tables.pop_table(opt_key) for opt_key in opt_keys]) self._tables.add_datatable("%soptical" % basename, opt_table) return self._tables def write_tables(self, filename): """ Write output fits tables """ if self._tables: self._tables.save_datatables(filename) @property def tables(self): """ Get the out put data tables""" return self._tables def print_summary(self, stream=sys.stdout): """ Print summary stats in humman readable format """ for key, val in self._sns_dict.items(): stream.write("%s ---------\n" % key) val.print_summary(stream) stream.write("---------\n") def print_optical_output(self, stream=sys.stdout): """ Print summary stats in humman readable format """ for key, val in self._sns_dict.items(): stream.write("%s ---------\n" % key) val.print_optical_output(stream) stream.write("---------\n") def run(self, universe, sim_cfg, basename=""): """ Run the analysis chain """ self.eval_sky(universe, sim_cfg.nsky_sim, sim_cfg.freq_resol) self.eval_instrument(sim_cfg.ndet_sim, sim_cfg.freq_resol) self.eval_sensitivities() save_summary = sim_cfg.save_summary if max(sim_cfg.nsky_sim, 1) * max(sim_cfg.ndet_sim, 1) == 1: save_summary = False self.make_tables(basename, save_summary=save_summary, save_sim=sim_cfg.save_sim, save_cfg=sim_cfg.save_optical)