NEDAS.utils.conversion module

NEDAS.utils.conversion.units_convert(units_from, units_to, var)[source]

Convert units for a given variable.

Parameters:
  • units_from (str or numbers.Number) – Source units for the input variable

  • units_to (str or numbers.Number) – Target units to convert to

  • var (np.ndarray) – The input variable

Returns:

Variable with converted units

Return type:

np.ndarray

NEDAS.utils.conversion.proj2dict(proj: Proj) dict[source]

Convert map projection name in pyproj.Proj to a dictionary of human-readable parameters

Parameters:

proj (pyproj.Proj) – Map projection object.

Returns:

A dictionary of projection parameters, such as name, lat_0, lon_0, etc.

Return type:

dict

NEDAS.utils.conversion.t2h(t: datetime) float[source]

Convert datetime object to hours since 1900-1-1 00:00

NEDAS.utils.conversion.h2t(h: float) datetime[source]

Convert hours since 1900-1-1 00:00 to datetime object

NEDAS.utils.conversion.t2s(t: datetime) str[source]

Convert datetime object to a time string 'ccyymmddHHMM'

NEDAS.utils.conversion.s2t(s: str) datetime[source]

Convert a time string 'ccyymmddHHMM' to a datetime object

NEDAS.utils.conversion.seconds_to_timestr(seconds: int) str[source]

Convert from seconds to time duration string ‘HH:MM:SS’

NEDAS.utils.conversion.ensure_list(v) list[source]

If the input v is a list, return itself; if not, return [v].

NEDAS.utils.conversion.resolve_iter_dict(value, iter: int, niter: int)[source]

Resolve a config value that may be given as a per-outer-loop-iteration override dict, keyed 'iter0', 'iter1', …, 'iter{niter-1}' (optionally plus a 'default' key).

Detection is purely by type (dict vs. anything else), never by list length or shape – so this can never collide with a value that is legitimately a list (or nested list) for some other, unrelated reason (e.g. a multiscale hcorr list for one perturbation variable, which has nothing to do with the outer loop). An earlier design that tried to infer per-iteration intent from len(value) == niter was rejected for exactly this reason – see NEDAS/DA-algorithms.md’s “Outer-loop design” notes.

If value is not a dict, it is returned unchanged: the same value is used at every outer-loop iteration (today’s behavior, for every existing config).

If value is a dict, every key must be either 'default' or match iterN with 0 <= N < niter – anything else (e.g. a typo like 'itr0', or an out-of-range index) raises immediately rather than being silently ignored. The current iteration’s own iterN key is used if present; otherwise 'default' is used if present; otherwise a ValueError is raised (no silent fallback to an unspecified value).

Parameters:
  • value – the config value to resolve (any type).

  • iter (int) – the current outer-loop iteration index.

  • niter (int) – the total number of outer-loop iterations.

Returns:

The resolved value for this iteration.

NEDAS.utils.conversion.is_iter_keyed_dict(value) bool[source]

True if value is a non-empty dict whose keys are ALL either ‘default’ or match ‘iterN’ – i.e. exactly the shape resolve_iter_dict() treats as a per-iteration override, as opposed to an ordinary dict whose keys mean something else entirely.

Needed only where the OLD format for a config value can ALREADY legitimately be a bare dict (e.g. transform_def’s single-transform-spec form, {‘type’: …, ‘decompose_obs’: …}) – there, resolve_iter_dict’s own type-based detection (dict vs. anything else) is not enough on its own, since a dict already has an established meaning at that nesting level; this lets a caller check the dict’s shape first, before deciding whether to run it through resolve_iter_dict at all. Most callers (hroi, err.std, assimilator_def/updator_def’s ‘type’, inflation_def’s ‘type’/’coef’/’adaptive’) never had a legitimate bare-dict old format, so they don’t need this – resolve_iter_dict’s detection is already unambiguous for them.

Parameters:

value – the config value to check (any type).

Returns:

True if value should be treated as a per-iteration override dict.

Return type:

bool

NEDAS.utils.conversion.expand_scale_dict(value)[source]

If value is a dict keyed 'scale0', 'scale1', …, 'scale{n-1}' (n>=1, no gaps, no stray keys), return the equivalent ORDERED LIST [value['scale0'], value['scale1'], ...].

This is an explicit alternative to inferring the number of scales from a bare list’s length (today’s existing convention for one perturbation variable’s own multiscale decomposition, PerturbField’s nscale) – it EXPANDS a keyed dict into the equivalent positional list every downstream consumer already expects, rather than SELECTING a single entry the way resolve_iter_dict does for the (separate, unrelated) DA-scheme outer loop – perturb’s own multiscale loop needs all nscale components at once (generate_perturb’s own for s in range(ns)), not one at a time.

If value is not such a dict, it is returned unchanged: a bare scalar (nscale=1) or a bare list (nscale=len(list), today’s existing convention) both keep working exactly as before – introducing this explicit dict form doesn’t create any new ambiguity at this nesting level, since a dict was never a valid value here before.

Parameters:

value – the config value to expand (any type).

Returns:

The expanded ordered list if value was a scale-keyed dict, otherwise value unchanged.