def __getattribute__(self, key): """Support deep and abbreviated lookup.""" # key = abbrevs[key] # Instead of this, also support rmse.a: key = '.'.join(Avrgs.abbrevs.get(seg, seg) for seg in key.split('.')) if "." in key: return struct_tools.deep_getattr(self, key) else: return super().__getattribute__(key)
def new_series(self, name, shape, length='FAUSt', MS=False, **kws): """Create (and register) a statistics time series. Series are initialized with nan's. Example ------- Create ndarray of length KObs+1 for inflation time series: >>> self.new_series('infl', 1, KObs+1) # doctest: +SKIP NB: The ``sliding_diagnostics`` liveplotting relies on detecting ``nan``'s to avoid plotting stats that are not being used. => Cannot use ``dtype=bool`` or ``int`` for stats that get plotted. """ # Convert int shape to tuple if not hasattr(shape, '__len__'): if shape == 1: shape = () else: shape = (shape, ) def make_series(parent, name, shape): if length == 'FAUSt': total_shape = self.K, self.KObs, shape store_opts = self.store_u, self.store_s tseries = series.FAUSt(*total_shape, *store_opts, **kws) else: total_shape = (length, ) + shape tseries = DataSeries(total_shape, *kws) register_stat(parent, name, tseries) # Principal series make_series(self, name, shape) # Summary (scalar) series: if shape != (): if MS: for suffix in self.field_summaries: make_series(getattr(self, name), suffix, ()) # Make a nested level for sectors if MS == 'sec': for ss in self.sector_summaries: suffix, sector = ss.split('.') make_series( struct_tools.deep_getattr(self, f"{name}.{suffix}"), sector, ())
def new_series(self, name, shape, length='FAUSt', field_mean=False, **kws): """Create (and register) a statistics time series, initialized with `nan`s. If `length` is an integer, a `DataSeries` (a trivial subclass of `numpy.ndarray`) is made. By default, though, a `series.FAUSt` is created. NB: The `sliding_diagnostics` liveplotting relies on detecting `nan`'s to avoid plotting stats that are not being used. Thus, you cannot use `dtype=bool` or `int` for stats that get plotted. """ # Convert int shape to tuple if not hasattr(shape, '__len__'): if shape == 1: shape = () else: shape = (shape, ) def make_series(parent, name, shape): if length == 'FAUSt': total_shape = self.K, self.Ko, shape store_opts = self.store_u, self.store_s tseries = series.FAUSt(*total_shape, *store_opts, **kws) else: total_shape = (length, ) + shape tseries = series.DataSeries(total_shape, *kws) register_stat(parent, name, tseries) # Principal series make_series(self, name, shape) # Summary (scalar) series: if shape != (): if field_mean: for suffix in self.field_summaries: make_series(getattr(self, name), suffix, ()) # Make a nested level for sectors if field_mean == 'sectors': for ss in self.sector_summaries: suffix, sector = ss.split('.') make_series( struct_tools.deep_getattr(self, f"{name}.{suffix}"), sector, ())
def finalize_init(ax, lines, mm): # Rm lines that only contain NaNs for name in list(lines): ln = lines[name] stat = deep_getattr(stats, name) if not stat.were_changed: ln['handle'].remove() # rm from axes del lines[name] # rm from dict # Add legends if lines: ax.legend(loc='upper left', bbox_to_anchor=(1.01, 1), borderaxespad=0) if mm: ax.annotate(star + ": mean of\nmarginals", xy=(0, -1.5 / len(lines)), xycoords=ax.get_legend().get_frame(), bbox=dict(alpha=0.0), fontsize='small') # coz placement of annotate needs flush sometimes: plot_pause(0.01)
def init_ax(ax, style_table): lines = {} for name in style_table: # SKIP -- if stats[name] is not in existence # Note: The nan check/deletion comes after the first ko. try: stat = deep_getattr(stats, name) except AttributeError: continue # try: val0 = stat[key0[0]] # except KeyError: continue # PS: recall (from series.py) that even if store_u is false, stat[k] is # still present if liveplots=True via the k_tmp functionality. # Unpack style ln = {} ln['transf'] = style_table[name][0] or (lambda x: x) ln['shape'] = style_table[name][1] ln['plt'] = style_table[name][2] # Create series if isinstance(stat, FAUSt): ln['plot_u'] = plot_u K_plot = comp_K_plot(K_lag, a_lag, ln['plot_u']) else: ln['plot_u'] = False K_plot = a_lag ln['data'] = RollingArray(K_plot) ln['tt'] = RollingArray(K_plot) # Plot (init) ln['handle'], = ax.plot(ln['tt'], ln['data'], **ln['plt']) # Plotting only nans yield ugly limits. Revert to defaults. ax.set_xlim(0, 1) ax.set_ylim(0, 1) lines[name] = ln return lines
def update_arrays(lines): for name, ln in lines.items(): stat = deep_getattr(stats, name) t = tseq.tt[k] # == tseq.tto[ko] if isinstance(stat, FAUSt): # ln['data'] will contain duplicates for f/a times. if ln['plot_u']: val = stat[key] ln['tt'].insert(k, t) ln['data'].insert(k, ln['transf'](val)) elif 'u' not in faus: val = stat[key] ln['tt'].insert(ko, t) ln['data'].insert(ko, ln['transf'](val)) else: # ln['data'] will not contain duplicates, coz only 'a' is input. if 'a' in faus: val = stat[ko] ln['tt'].insert(ko, t) ln['data'].insert(ko, ln['transf'](val)) elif 'f' in faus: pass
def assess(self, k, kObs=None, faus=None, E=None, w=None, mu=None, Cov=None): """Common interface for both assess_ens and _ext. The _ens assessment function gets called if E is not None, and _ext if mu is not None. faus: One or more of ['f',' a', 'u'], indicating that the result should be stored in (respectively) the forecast/analysis/universal attribute. Default: 'u' if kObs is None else 'au' ('a' and 'u'). """ # Initial consistency checks. if k == 0: if kObs is not None: raise KeyError("DAPPER convention: no obs at t=0." " Helps avoid bugs.") if faus is None: faus = 'u' if self._is_ens == True: if E is None: raise TypeError("Expected ensemble input but E is None") if mu is not None: raise TypeError( "Expected ensemble input but mu/Cov is not None") else: if E is not None: raise TypeError("Expected mu/Cov input but E is not None") if mu is None: raise TypeError("Expected mu/Cov input but mu is None") # Default. Don't add more defaults. It just gets confusing. if faus is None: faus = 'u' if kObs is None else 'au' # Select assessment call and arguments if self._is_ens: _assess = self.assess_ens _prms = {'E': E, 'w': w} else: _assess = self.assess_ext _prms = {'mu': mu, 'P': Cov} for sub in faus: # Skip assessment if ('u' and stats not stored or plotted) if k != 0 and kObs == None: if not (self.store_u or self.LP_instance.any_figs): continue # Silence repeat warnings caused by zero variance with np.errstate(divide='call', invalid='call'): np.seterrcall(warn_zero_variance) # Assess stats_now = Avrgs() _assess(stats_now, self.xx[k], **_prms) self.derivative_stats(stats_now) self.summarize_marginals(stats_now) # Write current stats to series for name, val in stats_now.items(): stat = struct_tools.deep_getattr(self, name) isFaust = isinstance(stat, series.FAUSt) stat[(k, kObs, sub) if isFaust else kObs] = val # LivePlot -- Both init and update must come after the assessment. try: self.LP_instance.update((k, kObs, sub), E, Cov) except AttributeError: self.LP_instance = liveplotting.LivePlot( self, self.liveplots, (k, kObs, sub), E, Cov)
def write(self, stat_dict, k, ko, sub): """Write `stat_dict` to series at `(k, ko, sub)`.""" for name, val in stat_dict.items(): stat = struct_tools.deep_getattr(self, name) isFaust = isinstance(stat, series.FAUSt) stat[(k, ko, sub) if isFaust else ko] = val
def get_val(i): try: return deep_getattr(xps[i].avrgs, 'err.rms.' + sub).val except AttributeError: return np.nan