tnfr 4.5.2__py3-none-any.whl → 8.5.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.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/utils/chunks.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Chunk sizing heuristics for batching structural computations.
|
|
2
|
+
|
|
3
|
+
The helpers in this module determine how large each processing block should be
|
|
4
|
+
when splitting work across workers or vectorised loops. They take into account
|
|
5
|
+
the number of items involved, approximate memory pressure, and available CPU
|
|
6
|
+
parallelism so the caller can balance throughput with deterministic behaviour.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
from typing import Final
|
|
14
|
+
|
|
15
|
+
DEFAULT_APPROX_BYTES_PER_ITEM: Final[int] = 64
|
|
16
|
+
DEFAULT_CHUNK_CLAMP: Final[int] | None = 131_072
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _estimate_available_memory() -> int | None:
|
|
20
|
+
"""Best-effort estimation of free memory available to the process."""
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
page_size = os.sysconf("SC_PAGE_SIZE")
|
|
24
|
+
avail_pages = os.sysconf("SC_AVPHYS_PAGES")
|
|
25
|
+
except (
|
|
26
|
+
AttributeError,
|
|
27
|
+
ValueError,
|
|
28
|
+
OSError,
|
|
29
|
+
): # pragma: no cover - platform specific
|
|
30
|
+
return None
|
|
31
|
+
if page_size <= 0 or avail_pages <= 0:
|
|
32
|
+
return None
|
|
33
|
+
return int(page_size) * int(avail_pages)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def auto_chunk_size(
|
|
37
|
+
total_items: int,
|
|
38
|
+
*,
|
|
39
|
+
minimum: int = 1,
|
|
40
|
+
approx_bytes_per_item: int = DEFAULT_APPROX_BYTES_PER_ITEM,
|
|
41
|
+
clamp_to: int | None = DEFAULT_CHUNK_CLAMP,
|
|
42
|
+
) -> int:
|
|
43
|
+
"""Infer a safe chunk length when the caller does not specify one."""
|
|
44
|
+
|
|
45
|
+
if total_items <= 0:
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
minimum = max(1, minimum)
|
|
49
|
+
approx_bytes_per_item = max(1, approx_bytes_per_item)
|
|
50
|
+
|
|
51
|
+
available_memory = _estimate_available_memory()
|
|
52
|
+
if available_memory is not None and available_memory > 0:
|
|
53
|
+
safe_bytes = max(approx_bytes_per_item * minimum, available_memory // 8)
|
|
54
|
+
mem_bound = max(minimum, min(total_items, safe_bytes // approx_bytes_per_item))
|
|
55
|
+
else:
|
|
56
|
+
mem_bound = total_items
|
|
57
|
+
|
|
58
|
+
if clamp_to is not None:
|
|
59
|
+
mem_bound = min(mem_bound, clamp_to)
|
|
60
|
+
|
|
61
|
+
cpu_count = os.cpu_count() or 1
|
|
62
|
+
target_chunks = max(1, cpu_count * 4)
|
|
63
|
+
cpu_chunk = max(minimum, math.ceil(total_items / target_chunks))
|
|
64
|
+
baseline = max(minimum, min(total_items, 1024))
|
|
65
|
+
target = max(cpu_chunk, baseline)
|
|
66
|
+
|
|
67
|
+
chunk = min(mem_bound, target)
|
|
68
|
+
chunk = max(minimum, min(total_items, chunk))
|
|
69
|
+
return chunk
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve_chunk_size(
|
|
73
|
+
chunk_size: int | None,
|
|
74
|
+
total_items: int,
|
|
75
|
+
*,
|
|
76
|
+
minimum: int = 1,
|
|
77
|
+
approx_bytes_per_item: int = DEFAULT_APPROX_BYTES_PER_ITEM,
|
|
78
|
+
clamp_to: int | None = DEFAULT_CHUNK_CLAMP,
|
|
79
|
+
) -> int:
|
|
80
|
+
"""Return a sanitised chunk size honouring automatic fallbacks."""
|
|
81
|
+
|
|
82
|
+
if total_items <= 0:
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
resolved: int | None
|
|
86
|
+
if chunk_size is None:
|
|
87
|
+
resolved = None
|
|
88
|
+
else:
|
|
89
|
+
try:
|
|
90
|
+
resolved = int(chunk_size)
|
|
91
|
+
except (TypeError, ValueError):
|
|
92
|
+
resolved = None
|
|
93
|
+
else:
|
|
94
|
+
if resolved <= 0:
|
|
95
|
+
resolved = None
|
|
96
|
+
|
|
97
|
+
if resolved is None:
|
|
98
|
+
resolved = auto_chunk_size(
|
|
99
|
+
total_items,
|
|
100
|
+
minimum=minimum,
|
|
101
|
+
approx_bytes_per_item=approx_bytes_per_item,
|
|
102
|
+
clamp_to=clamp_to,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return max(minimum, min(total_items, resolved))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = ["auto_chunk_size", "resolve_chunk_size"]
|
tnfr/utils/chunks.pyi
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
DEFAULT_APPROX_BYTES_PER_ITEM: Final[int]
|
|
6
|
+
DEFAULT_CHUNK_CLAMP: Final[int | None]
|
|
7
|
+
|
|
8
|
+
def auto_chunk_size(
|
|
9
|
+
total_items: int,
|
|
10
|
+
*,
|
|
11
|
+
minimum: int = ...,
|
|
12
|
+
approx_bytes_per_item: int = ...,
|
|
13
|
+
clamp_to: int | None = ...,
|
|
14
|
+
) -> int: ...
|
|
15
|
+
def resolve_chunk_size(
|
|
16
|
+
chunk_size: int | None,
|
|
17
|
+
total_items: int,
|
|
18
|
+
*,
|
|
19
|
+
minimum: int = ...,
|
|
20
|
+
approx_bytes_per_item: int = ...,
|
|
21
|
+
clamp_to: int | None = ...,
|
|
22
|
+
) -> int: ...
|
tnfr/utils/data.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""Utilities for manipulating collections and scalar values within TNFR."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import math
|
|
7
|
+
from collections import deque
|
|
8
|
+
from collections.abc import Collection, Iterable, Mapping, Sequence
|
|
9
|
+
from numbers import Real
|
|
10
|
+
from itertools import chain, islice
|
|
11
|
+
from typing import (
|
|
12
|
+
Any,
|
|
13
|
+
Callable,
|
|
14
|
+
Iterable as TypingIterable,
|
|
15
|
+
Iterator,
|
|
16
|
+
Literal,
|
|
17
|
+
TypeVar,
|
|
18
|
+
cast,
|
|
19
|
+
overload,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .numeric import kahan_sum_nd
|
|
23
|
+
from .init import get_logger
|
|
24
|
+
from .init import warn_once as _warn_once_factory
|
|
25
|
+
|
|
26
|
+
T = TypeVar("T")
|
|
27
|
+
|
|
28
|
+
_collections_logger = get_logger("tnfr.utils.data.collections")
|
|
29
|
+
_value_logger = get_logger("tnfr.utils.data")
|
|
30
|
+
|
|
31
|
+
STRING_TYPES = (str, bytes, bytearray)
|
|
32
|
+
|
|
33
|
+
NEGATIVE_WEIGHTS_MSG = "Negative weights detected: %s"
|
|
34
|
+
|
|
35
|
+
_MAX_NEGATIVE_WARN_ONCE = 1024
|
|
36
|
+
|
|
37
|
+
__all__ = (
|
|
38
|
+
"convert_value",
|
|
39
|
+
"normalize_optional_int",
|
|
40
|
+
"MAX_MATERIALIZE_DEFAULT",
|
|
41
|
+
"normalize_materialize_limit",
|
|
42
|
+
"is_non_string_sequence",
|
|
43
|
+
"flatten_structure",
|
|
44
|
+
"STRING_TYPES",
|
|
45
|
+
"ensure_collection",
|
|
46
|
+
"normalize_weights",
|
|
47
|
+
"negative_weights_warn_once",
|
|
48
|
+
"normalize_counter",
|
|
49
|
+
"mix_groups",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def convert_value(
|
|
54
|
+
value: Any,
|
|
55
|
+
conv: Callable[[Any], T],
|
|
56
|
+
*,
|
|
57
|
+
strict: bool = False,
|
|
58
|
+
key: str | None = None,
|
|
59
|
+
log_level: int | None = None,
|
|
60
|
+
) -> tuple[bool, T | None]:
|
|
61
|
+
"""Attempt to convert a value and report failures."""
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
converted = conv(value)
|
|
65
|
+
except (ValueError, TypeError) as exc:
|
|
66
|
+
if strict:
|
|
67
|
+
raise
|
|
68
|
+
level = log_level if log_level is not None else logging.DEBUG
|
|
69
|
+
if key is not None:
|
|
70
|
+
_value_logger.log(level, "Could not convert value for %r: %s", key, exc)
|
|
71
|
+
else:
|
|
72
|
+
_value_logger.log(level, "Could not convert value: %s", exc)
|
|
73
|
+
return False, None
|
|
74
|
+
if isinstance(converted, float) and not math.isfinite(converted):
|
|
75
|
+
if strict:
|
|
76
|
+
target = f"{key!r}" if key is not None else "value"
|
|
77
|
+
raise ValueError(f"Non-finite value {converted!r} for {target}")
|
|
78
|
+
level = log_level if log_level is not None else logging.DEBUG
|
|
79
|
+
if key is not None:
|
|
80
|
+
_value_logger.log(level, "Non-finite value for %r: %s", key, converted)
|
|
81
|
+
else:
|
|
82
|
+
_value_logger.log(level, "Non-finite value: %s", converted)
|
|
83
|
+
return False, None
|
|
84
|
+
return True, converted
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_DEFAULT_SENTINELS = frozenset({"auto", "none", "null"})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def normalize_optional_int(
|
|
91
|
+
value: Any,
|
|
92
|
+
*,
|
|
93
|
+
sentinels: Collection[str] | None = _DEFAULT_SENTINELS,
|
|
94
|
+
allow_non_positive: bool = True,
|
|
95
|
+
strict: bool = False,
|
|
96
|
+
error_message: str | None = None,
|
|
97
|
+
) -> int | None:
|
|
98
|
+
"""Normalise optional integers shared by CLI and runtime helpers.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
value:
|
|
103
|
+
Arbitrary object obtained from configuration, CLI options or graph
|
|
104
|
+
metadata.
|
|
105
|
+
sentinels:
|
|
106
|
+
Collection of case-insensitive strings that should be interpreted as
|
|
107
|
+
``None``. When ``None`` or empty, no sentinel mapping is applied.
|
|
108
|
+
allow_non_positive:
|
|
109
|
+
When ``False`` values ``<= 0`` are rejected and converted to ``None``.
|
|
110
|
+
strict:
|
|
111
|
+
When ``True`` invalid inputs raise :class:`ValueError` instead of
|
|
112
|
+
returning ``None``.
|
|
113
|
+
error_message:
|
|
114
|
+
Optional message used when ``strict`` mode raises due to invalid input
|
|
115
|
+
or disallowed non-positive values.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
if value is None:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
if isinstance(value, int):
|
|
122
|
+
result = value
|
|
123
|
+
elif isinstance(value, Real):
|
|
124
|
+
result = int(value)
|
|
125
|
+
else:
|
|
126
|
+
text = str(value).strip()
|
|
127
|
+
if not text:
|
|
128
|
+
if strict:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
error_message
|
|
131
|
+
or "Empty value is not allowed for configuration options."
|
|
132
|
+
)
|
|
133
|
+
return None
|
|
134
|
+
sentinel_set: set[str] | None = None
|
|
135
|
+
if sentinels:
|
|
136
|
+
sentinel_set = {s.lower() for s in sentinels}
|
|
137
|
+
lowered = text.lower()
|
|
138
|
+
if lowered in sentinel_set:
|
|
139
|
+
return None
|
|
140
|
+
try:
|
|
141
|
+
result = int(text)
|
|
142
|
+
except (TypeError, ValueError) as exc:
|
|
143
|
+
if strict:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
error_message or f"Invalid integer value: {value!r}"
|
|
146
|
+
) from exc
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
if not allow_non_positive and result <= 0:
|
|
150
|
+
if strict:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
error_message
|
|
153
|
+
or "Non-positive values are not permitted for this option."
|
|
154
|
+
)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def negative_weights_warn_once(
|
|
161
|
+
*, maxsize: int = _MAX_NEGATIVE_WARN_ONCE
|
|
162
|
+
) -> Callable[[Mapping[str, float]], None]:
|
|
163
|
+
"""Return a ``WarnOnce`` callable for negative weight warnings."""
|
|
164
|
+
|
|
165
|
+
return _warn_once_factory(
|
|
166
|
+
_collections_logger, NEGATIVE_WEIGHTS_MSG, maxsize=maxsize
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _log_negative_weights(negatives: Mapping[str, float]) -> None:
|
|
171
|
+
"""Log negative weight warnings without deduplicating keys."""
|
|
172
|
+
|
|
173
|
+
_collections_logger.warning(NEGATIVE_WEIGHTS_MSG, negatives)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _resolve_negative_warn_handler(
|
|
177
|
+
warn_once: bool | Callable[[Mapping[str, float]], None],
|
|
178
|
+
) -> Callable[[Mapping[str, float]], None]:
|
|
179
|
+
"""Return a callable that logs negative weight warnings."""
|
|
180
|
+
|
|
181
|
+
if callable(warn_once):
|
|
182
|
+
return warn_once
|
|
183
|
+
if warn_once:
|
|
184
|
+
return negative_weights_warn_once()
|
|
185
|
+
return _log_negative_weights
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def is_non_string_sequence(obj: Any) -> bool:
|
|
189
|
+
"""Return ``True`` if ``obj`` is an ``Iterable`` but not string-like or a mapping."""
|
|
190
|
+
|
|
191
|
+
return isinstance(obj, Iterable) and not isinstance(obj, (*STRING_TYPES, Mapping))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def flatten_structure(
|
|
195
|
+
obj: Any,
|
|
196
|
+
*,
|
|
197
|
+
expand: Callable[[Any], Iterable[Any] | None] | None = None,
|
|
198
|
+
) -> Iterator[Any]:
|
|
199
|
+
"""Yield leaf items from ``obj`` following breadth-first semantics."""
|
|
200
|
+
|
|
201
|
+
stack = deque([obj])
|
|
202
|
+
seen: set[int] = set()
|
|
203
|
+
while stack:
|
|
204
|
+
item = stack.pop()
|
|
205
|
+
item_id = id(item)
|
|
206
|
+
if item_id in seen:
|
|
207
|
+
continue
|
|
208
|
+
if expand is not None:
|
|
209
|
+
replacement = expand(item)
|
|
210
|
+
if replacement is not None:
|
|
211
|
+
seen.add(item_id)
|
|
212
|
+
stack.extendleft(replacement)
|
|
213
|
+
continue
|
|
214
|
+
if is_non_string_sequence(item):
|
|
215
|
+
seen.add(item_id)
|
|
216
|
+
stack.extendleft(item)
|
|
217
|
+
else:
|
|
218
|
+
yield item
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
MAX_MATERIALIZE_DEFAULT: int = 1000
|
|
222
|
+
"""Default materialization limit used by :func:`ensure_collection`."""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def normalize_materialize_limit(max_materialize: int | None) -> int | None:
|
|
226
|
+
"""Normalize and validate ``max_materialize`` returning a usable limit."""
|
|
227
|
+
|
|
228
|
+
if max_materialize is None:
|
|
229
|
+
return None
|
|
230
|
+
limit = int(max_materialize)
|
|
231
|
+
if limit < 0:
|
|
232
|
+
raise ValueError("'max_materialize' must be non-negative")
|
|
233
|
+
return limit
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@overload
|
|
237
|
+
def ensure_collection(
|
|
238
|
+
it: Iterable[T],
|
|
239
|
+
*,
|
|
240
|
+
max_materialize: int | None = MAX_MATERIALIZE_DEFAULT,
|
|
241
|
+
error_msg: str | None = None,
|
|
242
|
+
return_view: Literal[False] = False,
|
|
243
|
+
) -> Collection[T]: ...
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@overload
|
|
247
|
+
def ensure_collection(
|
|
248
|
+
it: Iterable[T],
|
|
249
|
+
*,
|
|
250
|
+
max_materialize: int | None = MAX_MATERIALIZE_DEFAULT,
|
|
251
|
+
error_msg: str | None = None,
|
|
252
|
+
return_view: Literal[True],
|
|
253
|
+
) -> tuple[Collection[T], TypingIterable[T]]: ...
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def ensure_collection(
|
|
257
|
+
it: Iterable[T],
|
|
258
|
+
*,
|
|
259
|
+
max_materialize: int | None = MAX_MATERIALIZE_DEFAULT,
|
|
260
|
+
error_msg: str | None = None,
|
|
261
|
+
return_view: bool = False,
|
|
262
|
+
) -> Collection[T] | tuple[Collection[T], TypingIterable[T]]:
|
|
263
|
+
"""Return ``it`` as a :class:`Collection`, materializing when needed.
|
|
264
|
+
|
|
265
|
+
When ``return_view`` is ``True`` the function returns a tuple containing the
|
|
266
|
+
materialised preview and an iterable that can be used to continue streaming
|
|
267
|
+
from the same source after the preview limit. The preview will contain up to
|
|
268
|
+
``max_materialize`` items (when the limit is enforced); when ``max_materialize``
|
|
269
|
+
is ``None`` the preview is empty and the returned iterable is the original
|
|
270
|
+
stream.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def _finalize(
|
|
274
|
+
collection: Collection[T],
|
|
275
|
+
view: TypingIterable[T] | None = None,
|
|
276
|
+
) -> Collection[T] | tuple[Collection[T], TypingIterable[T]]:
|
|
277
|
+
if not return_view:
|
|
278
|
+
return collection
|
|
279
|
+
if view is None:
|
|
280
|
+
return collection, collection
|
|
281
|
+
return collection, view
|
|
282
|
+
|
|
283
|
+
if isinstance(it, Collection):
|
|
284
|
+
if isinstance(it, STRING_TYPES):
|
|
285
|
+
wrapped = (cast(T, it),)
|
|
286
|
+
return _finalize(wrapped)
|
|
287
|
+
return _finalize(cast(Collection[T], it), cast(TypingIterable[T], it))
|
|
288
|
+
|
|
289
|
+
if isinstance(it, STRING_TYPES):
|
|
290
|
+
wrapped = (cast(T, it),)
|
|
291
|
+
return _finalize(wrapped)
|
|
292
|
+
|
|
293
|
+
if not isinstance(it, Iterable):
|
|
294
|
+
raise TypeError(f"{it!r} is not iterable")
|
|
295
|
+
|
|
296
|
+
limit = normalize_materialize_limit(max_materialize)
|
|
297
|
+
|
|
298
|
+
if return_view:
|
|
299
|
+
if limit is None:
|
|
300
|
+
return (), cast(TypingIterable[T], it)
|
|
301
|
+
if limit == 0:
|
|
302
|
+
return (), ()
|
|
303
|
+
|
|
304
|
+
iterator = iter(it)
|
|
305
|
+
preview = tuple(islice(iterator, limit + 1))
|
|
306
|
+
if len(preview) > limit:
|
|
307
|
+
examples = ", ".join(repr(x) for x in preview[:3])
|
|
308
|
+
msg = error_msg or (
|
|
309
|
+
f"Iterable produced {len(preview)} items, exceeds limit {limit}; first items: [{examples}]"
|
|
310
|
+
)
|
|
311
|
+
raise ValueError(msg)
|
|
312
|
+
if not preview:
|
|
313
|
+
return (), iterator
|
|
314
|
+
return preview, chain(preview, iterator)
|
|
315
|
+
|
|
316
|
+
if limit is None:
|
|
317
|
+
return tuple(it)
|
|
318
|
+
if limit == 0:
|
|
319
|
+
return ()
|
|
320
|
+
|
|
321
|
+
items = tuple(islice(it, limit + 1))
|
|
322
|
+
if len(items) > limit:
|
|
323
|
+
examples = ", ".join(repr(x) for x in items[:3])
|
|
324
|
+
msg = error_msg or (
|
|
325
|
+
f"Iterable produced {len(items)} items, exceeds limit {limit}; first items: [{examples}]"
|
|
326
|
+
)
|
|
327
|
+
raise ValueError(msg)
|
|
328
|
+
return items
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _convert_and_validate_weights(
|
|
332
|
+
dict_like: Mapping[str, Any],
|
|
333
|
+
keys: Iterable[str] | Sequence[str],
|
|
334
|
+
default: float,
|
|
335
|
+
*,
|
|
336
|
+
error_on_conversion: bool,
|
|
337
|
+
error_on_negative: bool,
|
|
338
|
+
warn_once: bool | Callable[[Mapping[str, float]], None],
|
|
339
|
+
) -> tuple[dict[str, float], list[str], float]:
|
|
340
|
+
"""Return converted weights, deduplicated keys and the accumulated total."""
|
|
341
|
+
|
|
342
|
+
keys_list = list(dict.fromkeys(keys))
|
|
343
|
+
default_float = float(default)
|
|
344
|
+
|
|
345
|
+
def convert(k: str) -> float:
|
|
346
|
+
ok, val = convert_value(
|
|
347
|
+
dict_like.get(k, default_float),
|
|
348
|
+
float,
|
|
349
|
+
strict=error_on_conversion,
|
|
350
|
+
key=k,
|
|
351
|
+
log_level=logging.WARNING,
|
|
352
|
+
)
|
|
353
|
+
return cast(float, val) if ok else default_float
|
|
354
|
+
|
|
355
|
+
weights = {k: convert(k) for k in keys_list}
|
|
356
|
+
negatives = {k: w for k, w in weights.items() if w < 0}
|
|
357
|
+
total = kahan_sum_nd(((w,) for w in weights.values()), dims=1)[0]
|
|
358
|
+
|
|
359
|
+
if negatives:
|
|
360
|
+
if error_on_negative:
|
|
361
|
+
raise ValueError(NEGATIVE_WEIGHTS_MSG % negatives)
|
|
362
|
+
warn_negative = _resolve_negative_warn_handler(warn_once)
|
|
363
|
+
warn_negative(negatives)
|
|
364
|
+
for key, weight in negatives.items():
|
|
365
|
+
weights[key] = 0.0
|
|
366
|
+
total -= weight
|
|
367
|
+
|
|
368
|
+
return weights, keys_list, total
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def normalize_weights(
|
|
372
|
+
dict_like: Mapping[str, Any],
|
|
373
|
+
keys: Iterable[str] | Sequence[str],
|
|
374
|
+
default: float = 0.0,
|
|
375
|
+
*,
|
|
376
|
+
error_on_negative: bool = False,
|
|
377
|
+
warn_once: bool | Callable[[Mapping[str, float]], None] = True,
|
|
378
|
+
error_on_conversion: bool = False,
|
|
379
|
+
) -> dict[str, float]:
|
|
380
|
+
"""Normalize ``keys`` in mapping ``dict_like`` so their sum is 1."""
|
|
381
|
+
|
|
382
|
+
weights, keys_list, total = _convert_and_validate_weights(
|
|
383
|
+
dict_like,
|
|
384
|
+
keys,
|
|
385
|
+
default,
|
|
386
|
+
error_on_conversion=error_on_conversion,
|
|
387
|
+
error_on_negative=error_on_negative,
|
|
388
|
+
warn_once=warn_once,
|
|
389
|
+
)
|
|
390
|
+
if not keys_list:
|
|
391
|
+
return {}
|
|
392
|
+
if total <= 0:
|
|
393
|
+
uniform = 1.0 / len(keys_list)
|
|
394
|
+
return {k: uniform for k in keys_list}
|
|
395
|
+
return {k: w / total for k, w in weights.items()}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def normalize_counter(
|
|
399
|
+
counts: Mapping[str, float | int],
|
|
400
|
+
) -> tuple[dict[str, float], float]:
|
|
401
|
+
"""Normalize a ``Counter`` returning proportions and total."""
|
|
402
|
+
|
|
403
|
+
total = kahan_sum_nd(((c,) for c in counts.values()), dims=1)[0]
|
|
404
|
+
if total <= 0:
|
|
405
|
+
return {}, 0
|
|
406
|
+
dist = {k: v / total for k, v in counts.items() if v}
|
|
407
|
+
return dist, total
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def mix_groups(
|
|
411
|
+
dist: Mapping[str, float],
|
|
412
|
+
groups: Mapping[str, Iterable[str]],
|
|
413
|
+
*,
|
|
414
|
+
prefix: str = "_",
|
|
415
|
+
) -> dict[str, float]:
|
|
416
|
+
"""Aggregate values of ``dist`` according to ``groups``."""
|
|
417
|
+
|
|
418
|
+
out: dict[str, float] = dict(dist)
|
|
419
|
+
out.update(
|
|
420
|
+
{
|
|
421
|
+
f"{prefix}{label}": kahan_sum_nd(
|
|
422
|
+
((dist.get(k, 0.0),) for k in keys),
|
|
423
|
+
dims=1,
|
|
424
|
+
)[0]
|
|
425
|
+
for label, keys in groups.items()
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
return out
|
tnfr/utils/data.pyi
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence
|
|
4
|
+
from typing import Any, Callable, Literal, TypeVar, overload
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
STRING_TYPES: tuple[type[str] | type[bytes] | type[bytearray], ...]
|
|
9
|
+
MAX_MATERIALIZE_DEFAULT: int
|
|
10
|
+
NEGATIVE_WEIGHTS_MSG: str
|
|
11
|
+
|
|
12
|
+
__all__: tuple[str, ...]
|
|
13
|
+
|
|
14
|
+
def convert_value(
|
|
15
|
+
value: Any,
|
|
16
|
+
conv: Callable[[Any], T],
|
|
17
|
+
*,
|
|
18
|
+
strict: bool = ...,
|
|
19
|
+
key: str | None = ...,
|
|
20
|
+
log_level: int | None = ...,
|
|
21
|
+
) -> tuple[bool, T | None]: ...
|
|
22
|
+
def normalize_optional_int(
|
|
23
|
+
value: Any,
|
|
24
|
+
*,
|
|
25
|
+
sentinels: Collection[str] | None = ...,
|
|
26
|
+
allow_non_positive: bool = ...,
|
|
27
|
+
strict: bool = ...,
|
|
28
|
+
error_message: str | None = ...,
|
|
29
|
+
) -> int | None: ...
|
|
30
|
+
def negative_weights_warn_once(
|
|
31
|
+
*,
|
|
32
|
+
maxsize: int = ...,
|
|
33
|
+
) -> Callable[[Mapping[str, float]], None]: ...
|
|
34
|
+
def is_non_string_sequence(obj: Any) -> bool: ...
|
|
35
|
+
def flatten_structure(
|
|
36
|
+
obj: Any,
|
|
37
|
+
*,
|
|
38
|
+
expand: Callable[[Any], Iterable[Any] | None] | None = ...,
|
|
39
|
+
) -> Iterator[Any]: ...
|
|
40
|
+
def normalize_materialize_limit(max_materialize: int | None) -> int | None: ...
|
|
41
|
+
@overload
|
|
42
|
+
def ensure_collection(
|
|
43
|
+
it: Iterable[T],
|
|
44
|
+
*,
|
|
45
|
+
max_materialize: int | None = ...,
|
|
46
|
+
error_msg: str | None = ...,
|
|
47
|
+
return_view: Literal[False] = ...,
|
|
48
|
+
) -> Collection[T]: ...
|
|
49
|
+
@overload
|
|
50
|
+
def ensure_collection(
|
|
51
|
+
it: Iterable[T],
|
|
52
|
+
*,
|
|
53
|
+
max_materialize: int | None = ...,
|
|
54
|
+
error_msg: str | None = ...,
|
|
55
|
+
return_view: Literal[True],
|
|
56
|
+
) -> tuple[Collection[T], Iterable[T]]: ...
|
|
57
|
+
def normalize_weights(
|
|
58
|
+
dict_like: Mapping[str, Any],
|
|
59
|
+
keys: Iterable[str] | Sequence[str],
|
|
60
|
+
default: float = ...,
|
|
61
|
+
*,
|
|
62
|
+
error_on_negative: bool = ...,
|
|
63
|
+
warn_once: bool | Callable[[Mapping[str, float]], None] = ...,
|
|
64
|
+
error_on_conversion: bool = ...,
|
|
65
|
+
) -> dict[str, float]: ...
|
|
66
|
+
def normalize_counter(
|
|
67
|
+
counts: Mapping[str, float | int],
|
|
68
|
+
) -> tuple[dict[str, float], float]: ...
|
|
69
|
+
def mix_groups(
|
|
70
|
+
dist: Mapping[str, float],
|
|
71
|
+
groups: Mapping[str, Iterable[str]],
|
|
72
|
+
*,
|
|
73
|
+
prefix: str = ...,
|
|
74
|
+
) -> dict[str, float]: ...
|