ialdev-core 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. iad/core/__init__.py +9 -0
  2. iad/core/array.py +1961 -0
  3. iad/core/binary.py +377 -0
  4. iad/core/cache.py +903 -0
  5. iad/core/codetools.py +203 -0
  6. iad/core/datatools.py +671 -0
  7. iad/core/docs/locators.ipynb +754 -0
  8. iad/core/dotstyle.py +99 -0
  9. iad/core/env.py +271 -0
  10. iad/core/events.py +650 -0
  11. iad/core/filesproc.py +1046 -0
  12. iad/core/fnctools.py +390 -0
  13. iad/core/label.py +240 -0
  14. iad/core/logs.py +182 -0
  15. iad/core/nptools.py +449 -0
  16. iad/core/one_dark.puml +881 -0
  17. iad/core/param/__init__.py +17 -0
  18. iad/core/param/confargparse.py +55 -0
  19. iad/core/param/paramaze.py +339 -0
  20. iad/core/param/tbox.py +277 -0
  21. iad/core/paths.py +563 -0
  22. iad/core/pdtools.py +2570 -0
  23. iad/core/pydantools/__init__.py +5 -0
  24. iad/core/pydantools/fixed_pydantic_yaml/__init__.py +32 -0
  25. iad/core/pydantools/fixed_pydantic_yaml/compat/__init__.py +0 -0
  26. iad/core/pydantools/fixed_pydantic_yaml/compat/hacks.py +76 -0
  27. iad/core/pydantools/fixed_pydantic_yaml/compat/old_enums.py +37 -0
  28. iad/core/pydantools/fixed_pydantic_yaml/compat/representers.py +92 -0
  29. iad/core/pydantools/fixed_pydantic_yaml/compat/types.py +122 -0
  30. iad/core/pydantools/fixed_pydantic_yaml/compat/yaml_lib.py +104 -0
  31. iad/core/pydantools/fixed_pydantic_yaml/ext/__init__.py +1 -0
  32. iad/core/pydantools/fixed_pydantic_yaml/ext/semver.py +152 -0
  33. iad/core/pydantools/fixed_pydantic_yaml/ext/versioned_model.py +113 -0
  34. iad/core/pydantools/fixed_pydantic_yaml/main.py +30 -0
  35. iad/core/pydantools/fixed_pydantic_yaml/mixin.py +281 -0
  36. iad/core/pydantools/fixed_pydantic_yaml/model.py +20 -0
  37. iad/core/pydantools/fixed_pydantic_yaml/py.typed +1 -0
  38. iad/core/pydantools/fixed_pydantic_yaml/version.py +1 -0
  39. iad/core/pydantools/models.py +560 -0
  40. iad/core/regexp.py +348 -0
  41. iad/core/short.py +308 -0
  42. iad/core/strings.py +635 -0
  43. iad/core/unc_panda.py +270 -0
  44. iad/core/units.py +58 -0
  45. iad/core/wrap.py +420 -0
  46. ialdev_core-0.1.0.dist-info/METADATA +73 -0
  47. ialdev_core-0.1.0.dist-info/RECORD +48 -0
  48. ialdev_core-0.1.0.dist-info/WHEEL +4 -0
iad/core/logs.py ADDED
@@ -0,0 +1,182 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from logging import (getLogger, Formatter, FileHandler, StreamHandler, _nameToLevel as nameToLevel,
5
+ DEBUG, INFO, FATAL, ERROR, WARNING, WARN)
6
+ from pathlib import Path
7
+
8
+ # noinspection PyUnresolvedReferences
9
+ __all__ = ['getLogger', 'Formatter', 'FileHandler', 'StreamHandler', 'logging', 'logger',
10
+ 'nameToLevel', 'DEBUG', 'INFO', 'FATAL', 'ERROR', 'WARNING', 'WARN']
11
+
12
+ from typing import TypeVar
13
+
14
+ prf_fmt = '%(relativeCreated)5d|%(name)13s.%(funcName)-14s|%(levelname)7s|%(message)s'
15
+ reg_fmt = '▷%(relativeCreated)5d|%(name)-14s|%(levelname)7s🚦%(message)s'
16
+ def_fmt = reg_fmt
17
+
18
+
19
+ def set_format(profile=False):
20
+ global def_fmt
21
+ fmt = prf_fmt if profile else reg_fmt
22
+ def_fmt = fmt
23
+ log = getLogger('root')
24
+ if not log.hasHandlers():
25
+ handler = StreamHandler()
26
+ log.addHandler(handler)
27
+ log.handlers[0].setFormatter(Formatter(fmt))
28
+
29
+
30
+ def_datefmt = '%H:%M:%S'
31
+ T = TypeVar('T')
32
+
33
+
34
+ def _add_levels_attrs(obj: T) -> T:
35
+ if not hasattr(obj, 'DEBUG'): # already assigned
36
+ for name, val in nameToLevel.items():
37
+ setattr(obj, name, val)
38
+ return obj
39
+
40
+
41
+ def module_log_file(file: str | Path):
42
+ """Constructs default log file name from module file"""
43
+ from .filesproc import Locator
44
+ out_folder = Locator('/tmp/ramdisk', '/tmp', envar='TEMP').first_existing()
45
+ if not out_folder:
46
+ from tempfile import mkdtemp
47
+ out_folder = Path(mkdtemp())
48
+
49
+ path = out_folder / 'logs' / f"{Path(file).name.split('.')[0]}.log"
50
+ msg = f"Log file: {str(path)}"
51
+ print(f"(!) ---> {msg}")
52
+ logging.getLogger().info(f'Log file: {msg}')
53
+ return path
54
+
55
+
56
+ def set_levels(logs_levels: dict[str, str | int] = None, *,
57
+ debug=None, error=None, info=None, warn=None, **levels_logs: str):
58
+ """
59
+ Set levels for multiple logs:
60
+
61
+ >>> set_levels(
62
+ ... {'deep_debug': 2},
63
+ ... debug = 'general',
64
+ ... info = ['scan', 'post'],
65
+ ... error ='root',
66
+ ... critical='another'
67
+ ... )
68
+
69
+ :param logs_levels:
70
+ :param levels_logs: {level: log(s)}
71
+ :param info: log or logs name(s)
72
+ :param debug: log or logs name(s)
73
+ :param warn: log or logs name(s)
74
+ :param error: log or logs name(s)
75
+ """
76
+ from .short import drop_undef
77
+
78
+ to_num = lambda lvl: nameToLevel[lvl.upper()] if isinstance(lvl, str) else lvl
79
+ levels_logs |= drop_undef('debug', 'error', 'info', 'warn', ns=locals())
80
+ levels_logs = {to_num(lvl): logs for lvl, logs in levels_logs.items()
81
+ } | {lvl: logs for logs, lvl in (logs_levels or {}).items()}
82
+
83
+ from . import as_iter
84
+ for level, logs in levels_logs.items():
85
+ for log in as_iter(logs):
86
+ getLogger(log).setLevel(level)
87
+
88
+
89
+ def logger(name: str = None, *, fmt=None, datefmt=None, level=None,
90
+ add_handler: str | Path | bool | None = False):
91
+ """
92
+ Create stream logger with given settings, or return an existing one
93
+ without altering.
94
+
95
+ :param name: name to by accessed by
96
+ :param fmt: handler formatting string
97
+ :param level: logger level
98
+ :param level: handler level
99
+ :param add_handler: if True - adds default stream handler
100
+ if str or Path - add
101
+ :return: logger
102
+ """
103
+ log = getLogger(name)
104
+
105
+ add_formatter = fmt or datefmt
106
+
107
+ if isinstance(add_handler, (str, Path)):
108
+ file = Path(add_handler).absolute()
109
+ for h in log.handlers:
110
+ if isinstance(h, FileHandler) and str(file) == h.baseFilename:
111
+ break
112
+ else:
113
+ file.parent.mkdir(exist_ok=True)
114
+ handler = FileHandler(file, mode='wt', encoding='utf-8')
115
+ log.addHandler(handler)
116
+ add_formatter = True
117
+ elif add_handler is True or add_handler is None and not log.hasHandlers():
118
+ handler = StreamHandler()
119
+ log.addHandler(handler)
120
+ add_formatter = True
121
+ elif add_handler is not False:
122
+ raise TypeError(f"Invalid {add_handler=}")
123
+
124
+ if add_formatter:
125
+ formatter = Formatter(fmt or def_fmt, datefmt=datefmt or def_datefmt)
126
+ for handler in log.handlers:
127
+ handler.setFormatter(formatter)
128
+
129
+ if level:
130
+ log.setLevel(level)
131
+
132
+ return _add_levels_attrs(log)
133
+
134
+
135
+ def setup_logs(*, file=None, name_from=None, profile=False, **levels_logs):
136
+ """
137
+ High level function to quickly set basic logs config
138
+ :param file: full path to the log file
139
+ :param name_from: <name> from this path is used: ../logs/<name>.log
140
+ :param levels_logs: {log_levels: loggers_names}
141
+ :param profile: if True set profiling friendly format
142
+ """
143
+ # TODO: what if all three arguments not given ?
144
+ if name_from:
145
+ if file: raise ValueError("Use either `file` OR `name_from` argument!")
146
+ file = module_log_file(name_from)
147
+
148
+ if file:
149
+ logger(None, add_handler=file)
150
+
151
+ if levels_logs:
152
+ set_levels(**levels_logs)
153
+
154
+ set_format(profile)
155
+
156
+
157
+ def error(err: BaseException | type[BaseException], msg: str | None = None,
158
+ fail: bool = True, level: int | str = 'ERROR',
159
+ logger: logging.Logger | str | None = None):
160
+ """
161
+ Routes errors into logger and optionally raise Exception.
162
+
163
+ :param err: Exception object ot type
164
+ :param msg: message to log / throw
165
+ :param fail: if True raise requested Exception
166
+ :param level: logging level
167
+ :param logger: logger or its name or None for root
168
+ """
169
+ if isinstance(err, type) and issubclass(err, BaseException):
170
+ err = err(msg) # create exception object given class, message
171
+ elif msg: # replace message in given exception object
172
+ err = type(err)(msg)
173
+ else: # extract message from exception object
174
+ msg = err.args
175
+
176
+ if logger is None or isinstance(logger, str):
177
+ logger = getLogger(logger)
178
+ if not isinstance(level, int):
179
+ level = nameToLevel[level]
180
+ logger.log(level, msg)
181
+ if fail:
182
+ raise err
iad/core/nptools.py ADDED
@@ -0,0 +1,449 @@
1
+ """ Convenience tools for NumPy package """
2
+
3
+ __all__ = ['var_filter', 'lindex', 'xy2i', 'Array', 'min_ids',
4
+ 'valid_in_range']
5
+
6
+ from typing import Union, Any
7
+
8
+ import numpy as np
9
+ from scipy import signal
10
+
11
+
12
+ class Array(np.ndarray):
13
+ _default_options = dict(
14
+ rows=16,
15
+ cols=12,
16
+ precision=3,
17
+ stats=1e6,
18
+ info=True
19
+ )
20
+
21
+ _print_options = _default_options.copy()
22
+
23
+ UNDEF = object()
24
+
25
+ @classmethod
26
+ def set_printoptions(self, rows=UNDEF, cols=UNDEF, stats=UNDEF, info=UNDEF,
27
+ precision=UNDEF, **options):
28
+ """
29
+
30
+ :param cols: maximal columns to show content
31
+ :param rows: maximal rows to show content
32
+ :param stats: maximal number of elements to calc stats for info
33
+ :param info: bool - show info summary, (None - automatic)
34
+ :param precision (one of np.setprintoptions)
35
+ :param options: other options for `np.set_printoptions`
36
+ :return:
37
+ """
38
+ options = {k: v for k, v in options.items() if k in np.get_printoptions()}
39
+
40
+ loc = locals()
41
+ defined = {k: v for k, v in loc.items() if k in self._default_options and v is not self.UNDEF}
42
+
43
+ self._print_options.update(**defined, **options)
44
+
45
+ def __repr__(self):
46
+ return array_info_func(**self._print_options)(self)
47
+
48
+ def __str__(self):
49
+ return self.__repr__()
50
+
51
+ def __new__(cls, obj, *, dtype=None, **kwargs):
52
+ return np.asarray(obj, dtype).view(cls)
53
+
54
+
55
+ def valid_in_range(m, min_v, max_v):
56
+ """Returns boolean mask
57
+ with True where m is not nan and in the given range.
58
+ Avoids Runtime warning on nan comparisons!
59
+ :param m: array
60
+ :param min_v: m >= min_v
61
+ :param max_v: m <= max_v
62
+ :return:
63
+ """
64
+ np.warnings.filterwarnings('ignore')
65
+ valid = m >= min_v
66
+ np.logical_and(valid, m < max_v, out=valid)
67
+ np.warnings.resetwarnings()
68
+ return valid
69
+
70
+
71
+ def var_filter(im, ker_sz):
72
+ """ Fast variance in a window filter (normalized by n*n-1)
73
+
74
+ :param im: 2D array
75
+ :param ker_sz: windows 1D size (will create window sz x sz)
76
+ :return: 2D array os same size as im
77
+ """
78
+ if hasattr(ker_sz, '__len__'):
79
+ assert len(ker_sz) == 2
80
+ var_win = ker_sz
81
+ else:
82
+ var_win = (ker_sz, ker_sz)
83
+ assert var_win[0] % 2 == 1 and var_win[1] % 2 == 1
84
+
85
+ flt = np.ones(var_win, dtype='int')
86
+ im = im.astype(float)
87
+ sum_x2 = signal.convolve2d(im ** 2, flt, mode='same', boundary='symm')
88
+ sum_x = signal.convolve2d(im, flt, mode='same', boundary='symm')
89
+ num = var_win[0] * var_win[1]
90
+ return (sum_x2 * num - sum_x ** 2) / (num ** 2)
91
+
92
+
93
+ def has_nans(a) -> bool:
94
+ # Checks if an array has any nans (fast implementation)
95
+ a = a.reshape(a.size)
96
+ return np.isnan(np.dot(a, a))
97
+
98
+
99
+ def lindex(coords):
100
+ """
101
+ Create index for n-dimensional array from array of N n-dimensional coordinates.
102
+ Coordinates may not be integer - they are converted to int inside
103
+ :param coords: (N x n) N - number of coordinates, n -dimension of array to index
104
+ :return: numpy nd array compatible index : tuple of tuples
105
+ """
106
+ return tuple(zip(*coords.astype(int)))
107
+
108
+
109
+ def xy2i(xy, y=None):
110
+ """
111
+ Translate into ij 2d-indexes various representation of set of x-y coordinates.
112
+
113
+ :param xy: x or array[N x (x,y)] or obj with attrs or items 'x', 'y'
114
+ :param y: if xy is x, then y - otherwise None!
115
+ :return: index for 2d arrays addressing tuple(is, js)
116
+
117
+ Examples:
118
+ xy2i(x, y) # numerical arrays of x, y coordinates
119
+
120
+ xy2i(xy_array) # convertible to Nx2 array of pairs [[x0, y0], ..., ]
121
+
122
+ xy2i(dict_xy) # object with items ['x'] and ['y']
123
+
124
+ xy2i(obj_xy) # object with attributes .x and .y
125
+
126
+ array [Nx2] of N 2-dimensional coordinates (x, y)
127
+
128
+ Create tuple(is, js) index selecting corresponding elements from a 2D array.
129
+ - values casted to int!
130
+
131
+
132
+ """
133
+ if y is None:
134
+ if hasattr(xy, 'x') and hasattr(xy, 'y'):
135
+ xy = xy.x, xy.y
136
+ elif hasattr(xy, '__getitem__') and 'x' in xy and 'y' in xy:
137
+ xy = xy['x'], xy['y']
138
+ else:
139
+ xy = np.array(xy, dtype=int).T
140
+ else:
141
+ xy = (xy, y)
142
+ return tuple(np.round(np.array(v)).astype(dtype=int) for v in xy[::-1])
143
+
144
+
145
+ def slice_1_axis(ndim, n, sl):
146
+ """ Create n-dim slice with given 1-d slice for given dimension
147
+ and rest of dimensions not sliced
148
+ :param ndim: number of dimensions
149
+ :param n: location of the slice dimension
150
+ :param sl: the slice to insert
151
+ """
152
+ assert 0 <= n < ndim
153
+ if ndim == 1:
154
+ return sl
155
+ ll = [slice(None), ] * (ndim - 1)
156
+ ll.insert(n, sl)
157
+ return tuple(ll)
158
+
159
+
160
+ def min_ids(a, n=1, *, axis=-1):
161
+ """
162
+ Find indices of n smallest elements along given axis.
163
+ Args:
164
+ a - array
165
+ n - number of best elements
166
+ axis - direction to sort by (default -1 to flatten)
167
+ Return:
168
+ For a.shape == N_0 ... N_axis ... N_dim, returns
169
+ array.shape == N_0 ... n ... N_dim
170
+ """
171
+ return np.argpartition(a, n - 1, axis=axis).take(range(n), axis=axis)
172
+
173
+
174
+ def abool(value: Union[Any, np.ndarray]) -> bool:
175
+ """ A convenience utility: boolean conversion which works on both scalars and numpy arrays.
176
+
177
+ For arrays returns:
178
+ Args:
179
+ value: any type supporting bool() or numpy array
180
+
181
+ Returns:
182
+ True | False
183
+
184
+ """
185
+ return value.all() if isinstance(value, np.ndarray) else bool(value)
186
+
187
+
188
+ def array_info_str(a, stats=1e7):
189
+ """
190
+ Short description of array, like:
191
+ [4×816×1200×7]f4 ⌊-30.53|454.01⌋ 0.2%NA
192
+
193
+ To be used with or formatters:
194
+ np.set_string_function(array_info_str)
195
+
196
+ :param a: array-like object
197
+ :param stats: maximal number of elements to add calculate statistics
198
+ """
199
+ sz = 1
200
+ for _ in a.shape: sz *= _ # to make it work for both numpy and torch tensors
201
+ s = '×'.join(map(str, a.shape)) or (f"val={a.item()}" if sz == 1 else '×')
202
+
203
+ if isinstance(a, np.ndarray):
204
+ kind = a.dtype.kind
205
+ s = f"[{s}]{kind}{a.dtype.itemsize}"
206
+ else: # torch! - No stats and fancy type info implemented yet
207
+ dtype = str(a.dtype)[6:].replace('float', 'f') # remove 'torch.'
208
+ return f"[{s}]{dtype}{(':c' if a.is_cuda else '')}"
209
+
210
+ if not 1 < sz < stats: return s
211
+
212
+ if kind == 'f':
213
+ _a = a[np.isfinite(a)]
214
+ mn, mx = np.min(_a), np.max(_a)
215
+ s = f"{s} ⌊{mn:.3g}|{mx:.3g}⌉"
216
+
217
+ def occurrence_info(name, num, num_lim=1000):
218
+ if not (num := int(num)):
219
+ return ''
220
+ ss = str(num) if num < num_lim else f"{num / a.size}%"
221
+ return f"{ss}{name}"
222
+
223
+ if occ := ', '.join(filter(None, [
224
+ occurrence_info('∅', nans := np.isnan(a).sum()),
225
+ occurrence_info('∞', a.size - _a.size - nans) # _a excluded both inf and nan!
226
+ ])): s += f"({occ})"
227
+
228
+ elif kind != 'O':
229
+ s += f" ⌊{a.min():.3g}|{a.max():.3g}⌉"
230
+
231
+ return s
232
+
233
+
234
+ def ascii_map(a, levels=None, show=False):
235
+ """
236
+ Unicode representation of array as map of shaded blocks.
237
+ :param a: array
238
+ :param levels: number of levels
239
+ :param show: if True - print debug info
240
+ :return: string (for printing)
241
+ """
242
+ import re
243
+ shades = '·⬚░▒▓█'
244
+ full = lambda smb: re.sub(r'[0\[\]]', ' ', str(np.full_like(a, 1, dtype=int)).replace('1', smb))
245
+
246
+ mx = a.max()
247
+ mn = a.min()
248
+
249
+ if not (np.isfinite(mn) and np.isfinite(mx)):
250
+ return full('⊗')
251
+ if mn == mx:
252
+ return full(shades[0] if mn == 0 else shades[-1])
253
+
254
+ ua = np.unique(a)
255
+ if levels is None:
256
+ levels = min(len(shades), len(ua))
257
+
258
+ if levels < 2:
259
+ return full()
260
+
261
+ max_level = levels - 1
262
+ assert max_level < len(shades)
263
+ idx = np.linspace(0, len(shades) - 1, max_level + 1, dtype=int)
264
+
265
+ levels = np.linspace(0, max_level, levels, dtype=int)
266
+ if len(ua) == max_level + 1:
267
+ la = np.empty_like(a, dtype=int)
268
+ for l, v in zip(levels, ua):
269
+ la[a == v] = l
270
+ # print(la)
271
+ s = str(la)
272
+ else:
273
+ s = str(((max_level / (mx - mn)) * (a - mn)).astype(int))
274
+
275
+ if show:
276
+ print('unique:', ua)
277
+ print('idx: ', idx)
278
+ print('levels:', levels)
279
+
280
+ for i, l in zip(idx, levels):
281
+ s = s.replace(str(l), shades[i])
282
+ return re.sub(r'[0\[\]]', ' ', s)
283
+
284
+
285
+ def pprint_arrays(do=True, **kws):
286
+ """
287
+ Set representation function of nd arrays and torch tensors
288
+ to ``array_info_func(**kws)``.
289
+
290
+ :param do: set to False to reset
291
+ :param kws: kwargs for array_info_func
292
+ """
293
+ if do:
294
+ func = array_info_func(**kws)
295
+ np.set_string_function(func)
296
+ pprint_tensors(do)
297
+ else:
298
+ np.set_string_function()
299
+ pprint_tensors(False)
300
+
301
+
302
+ def pprint_tensors(do=True):
303
+ """
304
+ Session level hack to replace (or restore)
305
+ ``Tensor.__repr__`` with ``array_info_func()``
306
+ """
307
+ try:
308
+ import torch
309
+ except ModuleNotFoundError:
310
+ from warnings import warn
311
+ do and warn("torch not found Can't hack Tensor repr")
312
+ return
313
+
314
+ if do:
315
+ torch.Tensor._original_repr = torch.Tensor.__repr__
316
+ torch.Tensor.__repr__ = array_info_func()
317
+ else:
318
+ torch.Tensor.__repr__ = torch.Tensor._original_repr
319
+
320
+
321
+ def array_info_func(cols=12, rows=8, *, stats=1e7, info=None, **options):
322
+ """ Return regular numpy array smart compact representation,
323
+ optionally including info summary (`array_info_str`).
324
+
325
+ :param cols: maximal columns to show content
326
+ :param rows: maximal rows to show content
327
+ :param stats: maximal number of elements to calc stats for info
328
+ :param info: bool - show info summary, (None - automatic)
329
+ :param options: for `set_printoptions`, like:
330
+ edgeitems=3, threshold=1000, floatmode=maxprec, precision=2,
331
+ suppress=False, linewidth=75
332
+ """
333
+
334
+ def content_repr(a):
335
+ if options:
336
+ prev = np.get_printoptions()
337
+ np.set_printoptions(**options)
338
+ s = np.ndarray.__str__(a)
339
+ np.set_printoptions(**prev)
340
+ return s
341
+ return np.ndarray.__str__(a)
342
+
343
+ def is_compact(last_dim, size):
344
+ return last_dim == 0 or (
345
+ 0 < last_dim <= cols and size / last_dim <= rows)
346
+
347
+ def full_repr(a):
348
+ show_content = a.ndim and (info is False or is_compact(
349
+ a.shape[-1], a.size if isinstance(a, np.ndarray) else a.numel()
350
+ ))
351
+ content = show_content and content_repr(a)
352
+ if content and not info: # return only content unless info is requested
353
+ return content
354
+
355
+ inf = array_info_str(a, stats=stats)
356
+ return f"{inf}:\n{content}" if content else inf
357
+
358
+ return full_repr
359
+
360
+
361
+ def stats(data, measure, out=str, prec=3, sep=', '):
362
+ """Calculate multiple metrics on data
363
+ :param data: array
364
+ :param measure: list of functions names from `numpy` module
365
+ first try to prepend `nan` to the name.
366
+ :param out: if `str` return result as string otherwise dict
367
+ ":param prec: precision of the fixed-point representation
368
+ """
369
+ import numpy as np
370
+ itr = [(msr, getattr(np, 'nan' + msr, getattr(np, msr))(data)) for msr in measure]
371
+ if out is str:
372
+ fmt = f"{{}}: {{:.{prec}f}}"
373
+ return sep.join(map(lambda mv: fmt.format(*mv), itr))
374
+ return dict(itr)
375
+
376
+
377
+ def cast_float(a, dtype=int, *, nan=0, inf='nan'):
378
+ """
379
+ From floating point array create integer typed and set nans and inf to specific values
380
+
381
+ :param a: array
382
+ :param nan: value to replace nans
383
+ :param inf: value to replace inf or
384
+ 'nan' - same as nan
385
+ 'clip' - clip by maximal positive or negative int
386
+ :param dtype: int type to cast into
387
+ :return: int array
388
+ """
389
+
390
+ res = a.astype(dtype)
391
+
392
+ if inf == 'nan':
393
+ res[~np.isfinite(a)] = nan
394
+ return res
395
+
396
+ res[np.isnan(a)] = nan
397
+ if inf == 'clip':
398
+ res[a == np.inf] = np.iinfo(dtype).max
399
+ res[a == -np.inf] = np.iinfo(dtype).min
400
+ else:
401
+ res[np.abs(a) == np.inf] = inf
402
+
403
+ return res
404
+
405
+
406
+ def fill_mask(a, mask, *, val=np.nan, inplace=False):
407
+ """
408
+ Fill array with elements where `~mask` set to `nan`
409
+ :param a:
410
+ :param mask: binary array describing (True) area to fill
411
+ :param val: value to fill the mask with (default - nan)
412
+ :param inplace: change and return the input array
413
+ :return:
414
+ """
415
+ na = a if inplace else a.copy()
416
+ na[mask] = val
417
+ return na
418
+
419
+
420
+ def full_indices(shape, *, base=1, dtype=int):
421
+ """
422
+ Create array of given shape full of values composed of its indices.
423
+
424
+ Example:
425
+
426
+ >>> full_indices([2,3])
427
+ array([[11, 21, 31],
428
+ [12, 22, 32]])
429
+
430
+ :param shape: shape to create
431
+ :param base: number to start the indexing from (usually 0 or 1)
432
+ :param dtype: of resulting array.
433
+ :return: array filled with index values
434
+ """
435
+ a = np.empty(shape, dtype=dtype)
436
+ p10 = np.power(10, np.arange(len(shape))).astype(int)
437
+ for idx, _ in np.ndenumerate(a):
438
+ a[idx] = ((np.array(idx) + base).astype(int) * p10).sum()
439
+ return a
440
+
441
+
442
+ def copy_nans(src, dst):
443
+ """Set nans found in source into same locations in dst """
444
+ if src.dtype.kind == 'f':
445
+ nans = np.isnan(src)
446
+ if np.count_nonzero(nans):
447
+ dst = dst.copy()
448
+ dst[nans] = np.nan
449
+ return dst