def _extract_params(params): '''Returns the basic parameters from a trellis `params` dict, converting them according to egsim needs. Returns the tuple: (gsim, imt, magnitudes, distances, trellisclass, stdev_trellisclass) where `trellis_class_for_stddev` can be None or the `trellis_class` counterpart for computing the standard deviations ''' MAG = 'magnitude' # pylint: disable=invalid-name DIST = 'distance' # pylint: disable=invalid-name GSIM = 'gsim' # pylint: disable=invalid-name IMT = 'imt' # pylint: disable=invalid-name STDEV = 'stdev' # pylint: disable=invalid-name PLOT_TYPE = 'plot_type' # pylint: disable=invalid-name # NOTE: the `params` dict will be passed to smtk routines: we use 'pop' # whenever possible to avoid passing unwanted params: gsim = params.pop(GSIM) # imt might be None for "spectra" Trellis classes, thus provide None: imt = params.pop(IMT, None) magnitudes = np.asarray(vectorize(params.pop(MAG))) # smtk wants np arrays distances = np.asarray(vectorize(params.pop(DIST))) # smtk wants np arrays trellisclass = params.pop(PLOT_TYPE) # define stddev trellis class if the parameter stdev is true stdev_trellisclass = None # do not compute stdev (default) if params.pop(STDEV, False): if trellisclass == DistanceIMTTrellis: stdev_trellisclass = DistanceSigmaIMTTrellis elif trellisclass == MagnitudeIMTTrellis: stdev_trellisclass = MagnitudeSigmaIMTTrellis elif trellisclass == MagnitudeDistanceSpectraTrellis: stdev_trellisclass = MagnitudeDistanceSpectraSigmaTrellis return gsim, imt, magnitudes, distances, trellisclass, stdev_trellisclass
def test_vectorize(): '''tests the vectorize function''' for arg in (None, '', 'abc', 1, 1.4005, True): expected = [arg] assert vectorize(arg) == expected assert vectorize(expected) is expected args = ([1, 2, 3], range(5), (1 for _ in [1, 2, 3])) for arg in args: assert vectorize(arg) is arg
def clean(self): cleaned_data = super(TrellisForm, self).clean() # calculate z1pt0 and z2pt5 if needed, raise in case of errors: vs30 = cleaned_data['vs30'] # surely a list with st least one element vs30scalar = isscalar(vs30) vs30s = np.array(vectorize(vs30), dtype=float) # check vs30-dependent values: for name, func in (['z1pt0', vs30_to_z1pt0_cy14], ['z2pt5', vs30_to_z2pt5_cb14]): if name not in cleaned_data or cleaned_data[name] == []: values = func(vs30s) # numpy-function cleaned_data[name] = \ float(values[0]) if vs30scalar else values.tolist() elif isscalar(cleaned_data[name]) != isscalar(vs30) or \ (not isscalar(vs30) and len(vs30) != len(cleaned_data[name])): str_ = 'scalar' if isscalar(vs30) else \ '%d-elements vector' % len(vs30) # instead of raising ValidationError, which is keyed with # '__all__' we add the error keyed to the given field name # `name` via `self.add_error`: # https://docs.djangoproject.com/en/2.0/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other error = ValidationError(_("value must be consistent with vs30 " "(%s)" % str_), code='invalid') self.add_error(name, error) return cleaned_data
def to_python(self, value): '''Converts the given input value to a Python list of IMT strings Remember that this method is called from within `self.clean` which in turns calls first `self.to_python` and then `self.validate`. The latter calls `self.valid_value` on each element of the input IMT list ''' # convert strings with wildcards to matching elements # (see MultipleChoiceWildcardField): imts = ImtclassField.to_python(self, value) # combine with separate SA periods, if provided periods_str = self.sa_periods_str if periods_str: try: saindex = imts.index(self.SA) except ValueError: saindex = len(imts) try: periods = \ vectorize(NArrayField(required=False).clean(periods_str)) ret = [_ for _ in imts if _ != self.SA] sa_str = '{}(%s)'.format(self.SA) sa_periods = [sa_str % _ for _ in periods] imts = ret[:saindex] + sa_periods + ret[saindex:] except Exception as _exc: raise ValidationError( self.error_messages['invalid_sa_period'], code='invalid_sa_period', params={'value': periods_str}, ) return imts
def to_python(self, value): # three scenarios: iterable: take iterable # non iterable: parse [value] # string: split value into iterable values = [] is_vector = not isscalar(value) if value is not None: if not is_vector and isinstance(value, str): value = value.strip() is_vector = value[:1] == '[' if is_vector != (value[-1:] == ']'): raise ValidationError('unbalanced brackets') try: value = json.loads(value if is_vector else "[%s]" % value) except Exception: # pylint: disable=broad-except try: value = shlex.split( value[1:-1].strip() if is_vector else value) except Exception: raise ValidationError('Input syntax error') for val in vectorize(value): try: vls = self.parse(val) except ValidationError: raise except Exception as exc: raise ValidationError("%s: %s" % (str(val), str(exc))) if isscalar(vls): values.append(vls) else: # force the return value to be list even if we have 1 elm: is_vector = True values.extend(vls) # check lengths: try: self.checkrange(len(values), self.min_count, self.max_count) except ValidationError as verr: # just re-format exception stringand raise: # msg should be in the form '% not in ...', remove first '%s' msg = verr.message[verr.message.find(' '):] raise ValidationError('number of elements (%d) %s' % (len(values), msg)) # check bounds: minval, maxval = self.min_value, self.max_value minval = [minval] * len(values) if isscalar(minval) else minval maxval = [maxval] * len(values) if isscalar(maxval) else maxval for numval, mnval, mxval in zip(values, chain(minval, repeat(None)), chain(maxval, repeat(None))): self.checkrange(numval, mnval, mxval) return values[0] if (len(values) == 1 and not is_vector) else values
def to_python(self, value): imts = ImtclassField.to_python(self, value) # combine with separate SA periods, if provided periods_str = self.sa_periods_str if periods_str: try: saindex = imts.index(self.SA) except ValueError: saindex = len(imts) try: periods = \ vectorize(NArrayField(required=False).clean(periods_str)) ret = [_ for _ in imts if _ != self.SA] sa_str = '{}(%s)'.format(self.SA) sa_periods = [sa_str % _ for _ in periods] imts = ret[:saindex] + sa_periods + ret[saindex:] except Exception as _: raise ValidationError( self.error_messages['invalid_sa_periods'], code='invalid_sa_periods') return imts
def get_trellis(params): # param names: MAG = 'magnitude' # pylint: disable=invalid-name DIST = 'distance' # pylint: disable=invalid-name VS30 = 'vs30' # pylint: disable=invalid-name Z1PT0 = 'z1pt0' # pylint: disable=invalid-name Z2PT5 = 'z2pt5' # pylint: disable=invalid-name GSIM = 'gsim' # pylint: disable=invalid-name IMT = 'imt' # pylint: disable=invalid-name # dip, aspect will be used below, we oparse them here because they are # mandatory (FIXME: are they?) magnitude, distance, vs30, z1pt0, z2pt5, gsim = \ params.pop(MAG), params.pop(DIST), params.pop(VS30), \ params.pop(Z1PT0), params.pop(Z2PT5), params.pop(GSIM) magnitudes = np.asarray(vectorize(magnitude)) # smtk wants numpy arrays distances = np.asarray(vectorize(distance)) # smtk wants numpy arrays vs30s = vectorize(vs30) z1pt0s = vectorize(z1pt0) z2pt5s = vectorize(z2pt5) trellisclass = params.pop('plot_type') isdist = trellisclass in (DistanceIMTTrellis, DistanceSigmaIMTTrellis) ismag = trellisclass in (MagnitudeIMTTrellis, MagnitudeSigmaIMTTrellis) isspectra = not isdist and not ismag if isspectra: # magnitudedistancetrellis: # imt is actually a vector of periods for the SA. This is misleading in # smtk, might be better implemented (maybe future PR?) imt = _default_periods_for_spectra() params.pop(IMT, None) # remove IMT and do not raise if not defined imt_names = ['SA'] else: imt = imt_names = params.pop(IMT) # this raises if IMT is not in dict def jsonserialize(value): '''serializes a numpy scalr into python scalar, no-op if value is not a numpy number''' try: return value.item() except AttributeError: return value ret = None figures = defaultdict(list) col_key, row_key = 'column', 'row' for vs30, z1pt0, z2pt5 in zip(vs30s, z1pt0s, z2pt5s): params[VS30] = vs30 params[Z1PT0] = z1pt0 params[Z2PT5] = z2pt5 # Depending on `trellisclass` we might need to iterate over # `magnitudes`, or use `magnitudes` once (the same holds for # `distances`). In order to make code cleaner we define a magnitude # iterator which yields a two element tuple (m1, m2) where m1 is the # scalar value to be saved as json, and m2 is the value # (scalar or array) to be passed to the Trellis class: magiter = zip(magnitudes, magnitudes) if isdist else \ zip([None], [magnitudes]) for mag, mags in magiter: # same as magnitudes (see above): distiter = zip(distances, distances) if ismag else \ zip([None], [distances]) for dist, dists in distiter: trellis_obj = trellisclass.from_rupture_properties( params, mags, dists, gsim, imt) data = trellis_obj.to_dict() # # data = { # xlabel: string # xvalues: list of floats # figures: [ # ylabel: # magnitude: # distance: # row: # column: # yvalues: # ] # } if ret is None: ret = { 'xlabel': _relabel_sa(data['xlabel']), 'xvalues': data['xvalues'] } # get the imt. Unfortunately, the imt is "hidden" # within each data.figures.ylabel, thus we have to # re-evaluate them using trellis_obj._get_ylabel, # which is what smtk uses if not isspectra: ylabel2imt = {trellis_obj._get_ylabel(_): _ for _ in imt} src_figures = data['figures'] for fig in src_figures: fig.pop(col_key, None) fig.pop(row_key, None) fig[VS30] = jsonserialize(vs30) fig[MAG] = jsonserialize(fig.get(MAG, mag)) fig[DIST] = jsonserialize(fig.get(DIST, dist)) imt_name = \ 'SA' if isspectra else ylabel2imt[fig['ylabel']] figures[imt_name].append(fig) # change labels SA(1.0000) into SA(1.0) but at the end # because the ylabel might have been used # as key (see line above) fig['ylabel'] = _relabel_sa(fig['ylabel']) # re-arrange labels FIXME: implement it in smtk? return {**ret, **{'imts': imt_names}, **figures}
def get_trellis(params): '''Core method to compute trellis plots data :param params: dict with the request parameters :return: json serializable dict to be passed into a Response object ''' # param names: MAG = 'magnitude' # pylint: disable=invalid-name DIST = 'distance' # pylint: disable=invalid-name VS30 = 'vs30' # pylint: disable=invalid-name Z1PT0 = 'z1pt0' # pylint: disable=invalid-name Z2PT5 = 'z2pt5' # pylint: disable=invalid-name gsim, imt, magnitudes, distances, trellisclass, stdev_trellisclass = \ _extract_params(params) xdata = None figures = defaultdict(list) # imt name -> list of dicts (1 dict=1 plot) for vs30, z1pt0, z2pt5 in zip(vectorize(params.pop(VS30)), vectorize(params.pop(Z1PT0)), vectorize(params.pop(Z2PT5))): params[VS30] = vs30 params[Z1PT0] = z1pt0 params[Z2PT5] = z2pt5 # Depending on `trellisclass` we might need to iterate over # `magnitudes`, or use `magnitudes` once (the same holds for # `distances`). In order to make code cleaner we define a magnitude # iterator which yields a two element tuple (m1, m2) where m1 is the # scalar value to be saved as json, and m2 is the value # (scalar or array) to be passed to the Trellis class: for mag, mags in zip(magnitudes, magnitudes) \ if _isdist(trellisclass) else zip([None], [magnitudes]): # same as magnitudes (see above): for dist, dists in zip(distances, distances) \ if _ismag(trellisclass) else zip([None], [distances]): data = _get_trellis_dict(trellisclass, params, mags, dists, gsim, imt) if xdata is None: xdata = { 'xlabel': _relabel_sa(data['xlabel']), 'xvalues': data['xvalues'] } _add_stdev( data, None if stdev_trellisclass is None else _get_trellis_dict( stdev_trellisclass, params, mags, dists, gsim, imt)) for fig in data['figures']: # Now we will modify 'fig' and eventually add it to # 'figures'. # 'fig' is a dict of this type: # (see method `_get_trellis_dict` and `_add_stdev` above): # { # ylabel: str # stdvalues: {} or dict gsimname -> list of numbers # stdlabel: str (might be empty str) # imt: str (the imt) # yvalues: dict (gsim name -> list of numbers) # } # 1) Add some keys to 'fig': fig[VS30] = _jsonserialize(vs30) fig[MAG] = _jsonserialize(fig.get(MAG, mag)) fig[DIST] = _jsonserialize(fig.get(DIST, dist)) # 4: Remove the imt of 'fig', and use it as key of # 'figures' figures[fig.pop('imt')].append(fig) # 'figures' is a dict of imt names mapped to a list of # dict. Each dict is one of the 'fig' just processed, # their count depends on the product of the chosen vs30, # mad and dist return { **xdata, 'imts': imt if (_ismag(trellisclass) or _isdist(trellisclass)) else ['SA'], **figures }