jarvisplot 1.3.8__tar.gz → 1.3.10__tar.gz
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.
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/PKG-INFO +2 -1
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/SOURCES.txt +6 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/PKG-INFO +2 -1
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/README.md +1 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/adapters_rect.py +25 -10
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/adapters_ternary.py +0 -8
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/colorbar_runtime.py +0 -1
- jarvisplot-1.3.10/jarvisplot/Figure/density_cell_runtime.py +427 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/figure.py +104 -4
- jarvisplot-1.3.10/jarvisplot/Figure/interp_2d_runtime.py +493 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/layer_runtime.py +147 -17
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/method_registry.py +0 -1
- jarvisplot-1.3.10/jarvisplot/Figure/posterior_hpd.py +159 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/preprocessor.py +167 -29
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/preprocessor_runtime.py +35 -41
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/profile_runtime.py +25 -13
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/core.py +5 -1
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/core_runtime.py +157 -9
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/data_loader_runtime.py +39 -20
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/expression.py +12 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/pyproject.toml +1 -1
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_data_loader_contracts.py +5 -5
- jarvisplot-1.3.10/tests/test_density_cell_transform.py +147 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_figure_contracts.py +114 -4
- jarvisplot-1.3.10/tests/test_interp_2d_transform.py +204 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_jpcontour_methods.py +22 -0
- jarvisplot-1.3.10/tests/test_posterior_hpd.py +161 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/entry_points.txt +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/requires.txt +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/JarvisPLOT.egg-info/top_level.txt +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/__init__.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/config_runtime.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/data_pipelines.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/dynesty_runtime.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/helper.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/layout_runtime.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/Figure/style_runtime.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/__init__.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cache_store.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/args.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/colors/colormaps.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/default/output.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/icons/gambit.png +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/icons/gambit_small.png +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cards/style_preference.json +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/cli.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/client.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/config.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/core_assets.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/data_loader.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/data_loader_hdf5.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/data_loader_summary.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/inner_func.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/memtrace.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/__init__.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/cmaps.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/dataframes.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/interpolator.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/jarvisplot/utils/pathing.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/setup.cfg +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_docs_status_labels.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_interp_natural_neighbor.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_interpolator_contracts.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_parse_data_metadata.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_pathing_contracts.py +0 -0
- {jarvisplot-1.3.8 → jarvisplot-1.3.10}/tests/test_template_json.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: JarvisPLOT
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.10
|
|
4
4
|
Summary: Jarvis-PLOT: YAML-driven plotting engine
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -117,6 +117,7 @@ and evidence summary behavior.
|
|
|
117
117
|
|
|
118
118
|
- Figures are saved automatically to the output paths defined in the YAML configuration.
|
|
119
119
|
- Common output formats include PNG and PDF (backend-dependent).
|
|
120
|
+
- Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
|
|
120
121
|
- Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
|
|
121
122
|
|
|
122
123
|
---
|
|
@@ -26,14 +26,17 @@ jarvisplot/Figure/adapters_ternary.py
|
|
|
26
26
|
jarvisplot/Figure/colorbar_runtime.py
|
|
27
27
|
jarvisplot/Figure/config_runtime.py
|
|
28
28
|
jarvisplot/Figure/data_pipelines.py
|
|
29
|
+
jarvisplot/Figure/density_cell_runtime.py
|
|
29
30
|
jarvisplot/Figure/dynesty_runtime.py
|
|
30
31
|
jarvisplot/Figure/figure.py
|
|
31
32
|
jarvisplot/Figure/helper.py
|
|
33
|
+
jarvisplot/Figure/interp_2d_runtime.py
|
|
32
34
|
jarvisplot/Figure/interp_natural_neighbor.py
|
|
33
35
|
jarvisplot/Figure/interp_natural_neighbor_exact.py
|
|
34
36
|
jarvisplot/Figure/layer_runtime.py
|
|
35
37
|
jarvisplot/Figure/layout_runtime.py
|
|
36
38
|
jarvisplot/Figure/method_registry.py
|
|
39
|
+
jarvisplot/Figure/posterior_hpd.py
|
|
37
40
|
jarvisplot/Figure/preprocessor.py
|
|
38
41
|
jarvisplot/Figure/preprocessor_runtime.py
|
|
39
42
|
jarvisplot/Figure/profile_runtime.py
|
|
@@ -68,11 +71,14 @@ jarvisplot/utils/expression.py
|
|
|
68
71
|
jarvisplot/utils/interpolator.py
|
|
69
72
|
jarvisplot/utils/pathing.py
|
|
70
73
|
tests/test_data_loader_contracts.py
|
|
74
|
+
tests/test_density_cell_transform.py
|
|
71
75
|
tests/test_docs_status_labels.py
|
|
72
76
|
tests/test_figure_contracts.py
|
|
77
|
+
tests/test_interp_2d_transform.py
|
|
73
78
|
tests/test_interp_natural_neighbor.py
|
|
74
79
|
tests/test_interpolator_contracts.py
|
|
75
80
|
tests/test_jpcontour_methods.py
|
|
76
81
|
tests/test_parse_data_metadata.py
|
|
77
82
|
tests/test_pathing_contracts.py
|
|
83
|
+
tests/test_posterior_hpd.py
|
|
78
84
|
tests/test_template_json.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: JarvisPLOT
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.10
|
|
4
4
|
Summary: Jarvis-PLOT: YAML-driven plotting engine
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -117,6 +117,7 @@ and evidence summary behavior.
|
|
|
117
117
|
|
|
118
118
|
- Figures are saved automatically to the output paths defined in the YAML configuration.
|
|
119
119
|
- Common output formats include PNG and PDF (backend-dependent).
|
|
120
|
+
- Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
|
|
120
121
|
- Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
|
|
121
122
|
|
|
122
123
|
---
|
|
@@ -97,6 +97,7 @@ and evidence summary behavior.
|
|
|
97
97
|
|
|
98
98
|
- Figures are saved automatically to the output paths defined in the YAML configuration.
|
|
99
99
|
- Common output formats include PNG and PDF (backend-dependent).
|
|
100
|
+
- Saved figures include file metadata such as `Creator: Jarvis-PLOT, powered by Jarvis-HEP` and `Jarvis-PLOT version: X.Y.Z`; the PNG `Description` also includes both fields for macOS Finder compatibility.
|
|
100
101
|
- Jarvis-PLOT works in headless environments (SSH, batch jobs) without any GUI backend.
|
|
101
102
|
|
|
102
103
|
---
|
|
@@ -11,8 +11,9 @@ from matplotlib.axes import Axes
|
|
|
11
11
|
from matplotlib.collections import PolyCollection
|
|
12
12
|
#
|
|
13
13
|
from .helper import _auto_clip, _mask_by_extend, voronoi_finite_polygons_2d, _clip_poly_to_rect
|
|
14
|
-
from .profile_runtime import
|
|
14
|
+
from .profile_runtime import regular_grid_mesh
|
|
15
15
|
from .interp_natural_neighbor import resolve_backend
|
|
16
|
+
from .posterior_hpd import prepare_hpd_contour_style
|
|
16
17
|
|
|
17
18
|
DEFAULT_JP_SAMPLE_GRID = 500
|
|
18
19
|
|
|
@@ -371,19 +372,20 @@ class StdAxesAdapter:
|
|
|
371
372
|
artists = self.ax.scatter(x, y, **kw)
|
|
372
373
|
return _auto_clip(artists, self.ax, self._clip_path)
|
|
373
374
|
|
|
374
|
-
def
|
|
375
|
-
"""
|
|
375
|
+
def pcolormesh(self, *args, **kwargs):
|
|
376
|
+
"""Render regular-grid fields, including compact tables with __grid_* metadata."""
|
|
377
|
+
if args:
|
|
378
|
+
kw = self._merge("pcolormesh", kwargs)
|
|
379
|
+
artist = self.ax.pcolormesh(*args, **kw)
|
|
380
|
+
return _auto_clip(artist, self.ax, self._clip_path)
|
|
376
381
|
|
|
377
|
-
Compared with tripcolor, this draws axis-aligned cells (like a regular
|
|
378
|
-
partition of the plane), closer to Voronoi-style region coloring.
|
|
379
|
-
"""
|
|
380
382
|
x = np.asarray(kwargs.pop("x"), dtype=float)
|
|
381
383
|
y = np.asarray(kwargs.pop("y"), dtype=float)
|
|
382
384
|
z = np.asarray(kwargs.pop("z", np.zeros_like(x)), dtype=float)
|
|
383
385
|
df = kwargs.pop("__df__", None)
|
|
384
|
-
kw = self._merge("
|
|
386
|
+
kw = self._merge("pcolormesh", kwargs)
|
|
385
387
|
|
|
386
|
-
# Strip kwargs that are not used by
|
|
388
|
+
# Strip kwargs that are not used by pcolormesh rendering.
|
|
387
389
|
for k in ("shading", "levels", "extend", "space", "marker", "s", "c"):
|
|
388
390
|
kw.pop(k, None)
|
|
389
391
|
|
|
@@ -411,7 +413,7 @@ class StdAxesAdapter:
|
|
|
411
413
|
xlim = [float(self.ax.get_xlim()[0]), float(self.ax.get_xlim()[1])]
|
|
412
414
|
if ylim is None:
|
|
413
415
|
ylim = [float(self.ax.get_ylim()[0]), float(self.ax.get_ylim()[1])]
|
|
414
|
-
mesh =
|
|
416
|
+
mesh = regular_grid_mesh(
|
|
415
417
|
x,
|
|
416
418
|
y,
|
|
417
419
|
z,
|
|
@@ -441,7 +443,6 @@ class StdAxesAdapter:
|
|
|
441
443
|
if zorder is not None:
|
|
442
444
|
kw.setdefault("zorder", zorder)
|
|
443
445
|
|
|
444
|
-
# histo2d-like rendering via pcolormesh
|
|
445
446
|
if norm is not None:
|
|
446
447
|
artist = self.ax.pcolormesh(
|
|
447
448
|
x_edges,
|
|
@@ -482,7 +483,13 @@ class StdAxesAdapter:
|
|
|
482
483
|
|
|
483
484
|
def contour(self, *args, **kwargs):
|
|
484
485
|
kw = self._merge("contour", kwargs)
|
|
486
|
+
label_map = kw.pop("_hpd_label_map", None)
|
|
485
487
|
artists = self.ax.contour(*args, **kw)
|
|
488
|
+
if label_map:
|
|
489
|
+
try:
|
|
490
|
+
self.ax.clabel(artists, fmt=label_map)
|
|
491
|
+
except Exception:
|
|
492
|
+
pass
|
|
486
493
|
return _auto_clip(artists, self.ax, self._clip_path)
|
|
487
494
|
|
|
488
495
|
def contourf(self, *args, **kwargs):
|
|
@@ -510,6 +517,7 @@ class StdAxesAdapter:
|
|
|
510
517
|
"""Scattered-data contour plot rendered in axes-fraction coordinates."""
|
|
511
518
|
kw = self._merge("contour", kwargs)
|
|
512
519
|
kw.pop("transform", None)
|
|
520
|
+
logger = kw.pop("_logger", None)
|
|
513
521
|
X, Y, Z = self._interpolate_jpcontour_grid(
|
|
514
522
|
x,
|
|
515
523
|
y,
|
|
@@ -524,6 +532,8 @@ class StdAxesAdapter:
|
|
|
524
532
|
diagnostics=diagnostics,
|
|
525
533
|
backend_options=backend_options,
|
|
526
534
|
)
|
|
535
|
+
kw = prepare_hpd_contour_style(Z, X, Y, kw, logger=logger)
|
|
536
|
+
label_map = kw.pop("_hpd_label_map", None)
|
|
527
537
|
artists = self.ax.contour(
|
|
528
538
|
X,
|
|
529
539
|
Y,
|
|
@@ -532,6 +542,11 @@ class StdAxesAdapter:
|
|
|
532
542
|
transform=self.ax.transAxes,
|
|
533
543
|
**kw,
|
|
534
544
|
)
|
|
545
|
+
if label_map:
|
|
546
|
+
try:
|
|
547
|
+
self.ax.clabel(artists, fmt=label_map)
|
|
548
|
+
except Exception:
|
|
549
|
+
pass
|
|
535
550
|
return _auto_clip(artists, self.ax, self._clip_path)
|
|
536
551
|
|
|
537
552
|
def jpcontourf(
|
|
@@ -54,14 +54,6 @@ class TernaryAxesAdapter(StdAxesAdapter):
|
|
|
54
54
|
kwargs['y'] = y
|
|
55
55
|
return super().scatter(**kwargs)
|
|
56
56
|
|
|
57
|
-
def grid_profile(self, **kwargs):
|
|
58
|
-
if {"left", "right", "bottom"}.issubset(kwargs.keys()):
|
|
59
|
-
x, y = self._lbr_to_xy(kwargs.pop('left'), kwargs.pop('right'), kwargs.pop('bottom'))
|
|
60
|
-
kwargs['x'] = x
|
|
61
|
-
kwargs['y'] = y
|
|
62
|
-
return super().grid_profile(**kwargs)
|
|
63
|
-
|
|
64
|
-
|
|
65
57
|
def plot(self, **kwargs):
|
|
66
58
|
if {"left", "right", "bottom"}.issubset(kwargs.keys()):
|
|
67
59
|
x, y = self._lbr_to_xy(kwargs.pop('left'), kwargs.pop('right'), kwargs.pop('bottom'))
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Mapping, Sequence, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from ..memtrace import memtrace_checkpoint
|
|
10
|
+
from ..utils.expression import eval_dataframe_expression
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_DENSITY_CELL_TYPES = {"make_density_core"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _ScaledBwMethod:
|
|
17
|
+
def __init__(self, factor: float, base: str):
|
|
18
|
+
self.factor = float(factor)
|
|
19
|
+
self.base = str(base).strip().lower()
|
|
20
|
+
|
|
21
|
+
def __call__(self, kde) -> float:
|
|
22
|
+
if self.base == "silverman":
|
|
23
|
+
return self.factor * float(kde.silverman_factor())
|
|
24
|
+
return self.factor * float(kde.scotts_factor())
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
return f"{self.factor:g}*{self.base}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_density_cell_transform(step: Any) -> bool:
|
|
31
|
+
if not isinstance(step, Mapping):
|
|
32
|
+
return False
|
|
33
|
+
if any(name in step for name in _DENSITY_CELL_TYPES):
|
|
34
|
+
return True
|
|
35
|
+
return str(step.get("type", "")).strip().lower() in _DENSITY_CELL_TYPES
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def density_cell_config(step: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
39
|
+
for name in _DENSITY_CELL_TYPES:
|
|
40
|
+
if name in step:
|
|
41
|
+
cfg = step.get(name)
|
|
42
|
+
return cfg if isinstance(cfg, Mapping) else {}
|
|
43
|
+
if str(step.get("type", "")).strip().lower() in _DENSITY_CELL_TYPES:
|
|
44
|
+
return step
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _logger_emit(logger, level: str, message: str) -> None:
|
|
49
|
+
if logger is None:
|
|
50
|
+
return
|
|
51
|
+
try:
|
|
52
|
+
getattr(logger, level)(message)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _coord_cfg(cfg: Mapping[str, Any], key: str) -> Mapping[str, Any]:
|
|
58
|
+
coors = cfg.get("coordinates", {})
|
|
59
|
+
if not isinstance(coors, Mapping):
|
|
60
|
+
coors = {}
|
|
61
|
+
spec = coors.get(key, cfg.get(key, None))
|
|
62
|
+
if isinstance(spec, Mapping):
|
|
63
|
+
return spec
|
|
64
|
+
if spec is None:
|
|
65
|
+
return {}
|
|
66
|
+
return {"name": str(spec)}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _output_name(cfg: Mapping[str, Any], key: str, default: str) -> str:
|
|
70
|
+
name = _coord_cfg(cfg, key).get("name", default)
|
|
71
|
+
text = str(name).strip()
|
|
72
|
+
return text or default
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _resolve_array(df: pd.DataFrame, cfg: Mapping[str, Any], key: str, default: str, logger, *, required: bool) -> np.ndarray:
|
|
76
|
+
spec = _coord_cfg(cfg, key)
|
|
77
|
+
expr = spec.get("expr")
|
|
78
|
+
if expr is not None:
|
|
79
|
+
return np.asarray(eval_dataframe_expression(df, expr, logger=logger, allow_column=True), dtype=float)
|
|
80
|
+
name = str(spec.get("name", default)).strip()
|
|
81
|
+
if not name:
|
|
82
|
+
if required:
|
|
83
|
+
raise ValueError(f"make_density_core requires a non-empty coordinate for '{key}'.")
|
|
84
|
+
return np.ones(int(df.shape[0]), dtype=float)
|
|
85
|
+
if name not in df.columns:
|
|
86
|
+
if required:
|
|
87
|
+
raise KeyError(f"make_density_core missing required column '{name}'.")
|
|
88
|
+
return np.ones(int(df.shape[0]), dtype=float)
|
|
89
|
+
return np.asarray(df[name], dtype=float)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _axis_lim(values: np.ndarray, cfg: Mapping[str, Any], key: str) -> Tuple[float, float]:
|
|
93
|
+
spec = _coord_cfg(cfg, key)
|
|
94
|
+
lim = spec.get("lim", spec.get("limits", None))
|
|
95
|
+
domain = cfg.get("domain", {})
|
|
96
|
+
if not isinstance(domain, Mapping):
|
|
97
|
+
domain = {}
|
|
98
|
+
if lim is None:
|
|
99
|
+
lim = domain.get(f"{key}lim", cfg.get(f"{key}lim", None))
|
|
100
|
+
if lim is None:
|
|
101
|
+
finite = np.asarray(values, dtype=float)
|
|
102
|
+
finite = finite[np.isfinite(finite)]
|
|
103
|
+
if finite.size == 0:
|
|
104
|
+
raise ValueError(f"make_density_core cannot infer {key} limits from non-finite data.")
|
|
105
|
+
lo = float(np.min(finite))
|
|
106
|
+
hi = float(np.max(finite))
|
|
107
|
+
if lo == hi:
|
|
108
|
+
hi = lo + 1.0
|
|
109
|
+
return lo, hi
|
|
110
|
+
if not isinstance(lim, Sequence) or len(lim) != 2:
|
|
111
|
+
raise ValueError(f"make_density_core coordinates.{key}.lim must have two values.")
|
|
112
|
+
lo, hi = float(lim[0]), float(lim[1])
|
|
113
|
+
if not (np.isfinite(lo) and np.isfinite(hi) and hi > lo):
|
|
114
|
+
raise ValueError(f"make_density_core coordinates.{key}.lim requires finite lo < hi.")
|
|
115
|
+
return lo, hi
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _axis_scale(cfg: Mapping[str, Any], key: str) -> str:
|
|
119
|
+
spec = _coord_cfg(cfg, key)
|
|
120
|
+
domain = cfg.get("domain", {})
|
|
121
|
+
if not isinstance(domain, Mapping):
|
|
122
|
+
domain = {}
|
|
123
|
+
return str(spec.get("scale", domain.get(f"{key}scale", cfg.get(f"{key}scale", "linear")))).strip().lower()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _project_axis(values: np.ndarray, lim: Tuple[float, float], scale: str, key: str) -> np.ndarray:
|
|
127
|
+
lo, hi = lim
|
|
128
|
+
values = np.asarray(values, dtype=float)
|
|
129
|
+
if scale == "log":
|
|
130
|
+
if lo <= 0 or hi <= 0:
|
|
131
|
+
raise ValueError(f"make_density_core {key} log scale requires positive limits.")
|
|
132
|
+
out = np.full(values.shape, np.nan, dtype=float)
|
|
133
|
+
mask = values > 0
|
|
134
|
+
out[mask] = (np.log(values[mask]) - np.log(lo)) / (np.log(hi) - np.log(lo))
|
|
135
|
+
return out
|
|
136
|
+
return (values - lo) / (hi - lo)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _unproject_axis(values: np.ndarray, lim: Tuple[float, float], scale: str) -> np.ndarray:
|
|
140
|
+
lo, hi = lim
|
|
141
|
+
values = np.asarray(values, dtype=float)
|
|
142
|
+
if scale == "log":
|
|
143
|
+
return np.exp(np.log(lo) + values * (np.log(hi) - np.log(lo)))
|
|
144
|
+
return lo + values * (hi - lo)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _grid_shape(cfg: Mapping[str, Any], default: int = 128) -> Tuple[int, int]:
|
|
148
|
+
grid = cfg.get("grid", {})
|
|
149
|
+
if not isinstance(grid, Mapping):
|
|
150
|
+
grid = {}
|
|
151
|
+
bins = grid.get("bins", grid.get("bin", cfg.get("bins", cfg.get("bin", None))))
|
|
152
|
+
if bins is not None:
|
|
153
|
+
nx = ny = int(bins)
|
|
154
|
+
else:
|
|
155
|
+
nx = int(grid.get("nx", cfg.get("nx", default)))
|
|
156
|
+
ny = int(grid.get("ny", cfg.get("ny", default)))
|
|
157
|
+
if nx < 2 or ny < 2:
|
|
158
|
+
raise ValueError("make_density_core grid requires nx >= 2 and ny >= 2.")
|
|
159
|
+
return nx, ny
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _bw_method(cfg: Mapping[str, Any]):
|
|
163
|
+
bw = cfg.get("bw_method", cfg.get("bandwidth", None))
|
|
164
|
+
if bw is None:
|
|
165
|
+
return None
|
|
166
|
+
if isinstance(bw, str):
|
|
167
|
+
text = bw.strip().lower()
|
|
168
|
+
if text in {"", "default", "none"}:
|
|
169
|
+
return None
|
|
170
|
+
if text in {"scott", "silverman"}:
|
|
171
|
+
return text
|
|
172
|
+
match = re.fullmatch(r"([0-9]*\.?[0-9]+)\s*\*\s*(scott|silverman)", text)
|
|
173
|
+
if match:
|
|
174
|
+
factor = float(match.group(1))
|
|
175
|
+
if not np.isfinite(factor) or factor <= 0:
|
|
176
|
+
raise ValueError("make_density_core scaled bw_method factor must be positive and finite.")
|
|
177
|
+
return _ScaledBwMethod(factor, match.group(2))
|
|
178
|
+
value = float(text)
|
|
179
|
+
if not np.isfinite(value) or value <= 0:
|
|
180
|
+
raise ValueError("make_density_core float bw_method must be positive and finite.")
|
|
181
|
+
return value
|
|
182
|
+
value = float(bw)
|
|
183
|
+
if not np.isfinite(value) or value <= 0:
|
|
184
|
+
raise ValueError("make_density_core float bw_method must be positive and finite.")
|
|
185
|
+
return value
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _prepare_samples(df: pd.DataFrame, cfg: Mapping[str, Any], logger):
|
|
189
|
+
x = _resolve_array(df, cfg, "x", "x", logger, required=True)
|
|
190
|
+
y = _resolve_array(df, cfg, "y", "y", logger, required=True)
|
|
191
|
+
weight_required = bool(_coord_cfg(cfg, "weight"))
|
|
192
|
+
w = _resolve_array(df, cfg, "weight", "weight", logger, required=weight_required)
|
|
193
|
+
n = min(len(x), len(y), len(w), int(df.shape[0]))
|
|
194
|
+
x = np.asarray(x[:n], dtype=float)
|
|
195
|
+
y = np.asarray(y[:n], dtype=float)
|
|
196
|
+
w = np.asarray(w[:n], dtype=float)
|
|
197
|
+
valid = np.isfinite(x) & np.isfinite(y) & np.isfinite(w) & (w > 0)
|
|
198
|
+
if not np.any(valid):
|
|
199
|
+
raise ValueError("make_density_core got no finite samples with positive finite weights.")
|
|
200
|
+
if not np.all(valid):
|
|
201
|
+
_logger_emit(logger, "warning", f"make_density_core dropped {int((~valid).sum())} invalid or non-positive samples.")
|
|
202
|
+
return x[valid], y[valid], w[valid], n
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _normalize(values: np.ndarray, *, enabled: bool) -> np.ndarray:
|
|
206
|
+
values = np.asarray(values, dtype=float)
|
|
207
|
+
if not enabled:
|
|
208
|
+
return values
|
|
209
|
+
total = float(np.sum(values))
|
|
210
|
+
if not np.isfinite(total) or total <= 0:
|
|
211
|
+
raise ValueError("make_density_core cannot normalize non-positive or non-finite total weight.")
|
|
212
|
+
return values / total
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _grid_density_cell(x, y, w, cfg, logger):
|
|
216
|
+
xlim = _axis_lim(x, cfg, "x")
|
|
217
|
+
ylim = _axis_lim(y, cfg, "y")
|
|
218
|
+
xscale = _axis_scale(cfg, "x")
|
|
219
|
+
yscale = _axis_scale(cfg, "y")
|
|
220
|
+
nx, ny = _grid_shape(cfg)
|
|
221
|
+
xp = _project_axis(x, xlim, xscale, "x")
|
|
222
|
+
yp = _project_axis(y, ylim, yscale, "y")
|
|
223
|
+
inside = np.isfinite(xp) & np.isfinite(yp) & (xp >= 0.0) & (xp <= 1.0) & (yp >= 0.0) & (yp <= 1.0)
|
|
224
|
+
if not np.any(inside):
|
|
225
|
+
raise ValueError("make_density_core grid found no positive-mass samples inside the domain.")
|
|
226
|
+
if not np.all(inside):
|
|
227
|
+
_logger_emit(logger, "warning", f"make_density_core grid ignored {int((~inside).sum())} samples outside the domain.")
|
|
228
|
+
ix = np.clip(xp[inside], 0.0, 1.0 - 1e-12)
|
|
229
|
+
iy = np.clip(yp[inside], 0.0, 1.0 - 1e-12)
|
|
230
|
+
ix = (ix * nx).astype(np.int32)
|
|
231
|
+
iy = (iy * ny).astype(np.int32)
|
|
232
|
+
mass = np.zeros((ny, nx), dtype=float)
|
|
233
|
+
np.add.at(mass, (iy, ix), w[inside])
|
|
234
|
+
gx = (np.arange(nx, dtype=float) + 0.5) / float(nx)
|
|
235
|
+
gy = (np.arange(ny, dtype=float) + 0.5) / float(ny)
|
|
236
|
+
xg, yg = np.meshgrid(_unproject_axis(gx, xlim, xscale), _unproject_axis(gy, ylim, yscale))
|
|
237
|
+
return xg.ravel(), yg.ravel(), mass.ravel()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _bridson_points(radius: float, *, seed: int | None = None, k: int = 30) -> np.ndarray:
|
|
241
|
+
rng = np.random.default_rng(seed)
|
|
242
|
+
cell = radius / np.sqrt(2.0)
|
|
243
|
+
nx = max(1, int(np.ceil(1.0 / cell)))
|
|
244
|
+
ny = max(1, int(np.ceil(1.0 / cell)))
|
|
245
|
+
grid = -np.ones((ny, nx), dtype=np.int64)
|
|
246
|
+
points: list[tuple[float, float]] = []
|
|
247
|
+
active: list[int] = []
|
|
248
|
+
|
|
249
|
+
def _grid_pos(pt):
|
|
250
|
+
gx = min(nx - 1, max(0, int(pt[0] / cell)))
|
|
251
|
+
gy = min(ny - 1, max(0, int(pt[1] / cell)))
|
|
252
|
+
return gx, gy
|
|
253
|
+
|
|
254
|
+
def _far_enough(pt) -> bool:
|
|
255
|
+
gx, gy = _grid_pos(pt)
|
|
256
|
+
r2 = radius * radius
|
|
257
|
+
for yy in range(max(0, gy - 2), min(ny, gy + 3)):
|
|
258
|
+
for xx in range(max(0, gx - 2), min(nx, gx + 3)):
|
|
259
|
+
idx = int(grid[yy, xx])
|
|
260
|
+
if idx < 0:
|
|
261
|
+
continue
|
|
262
|
+
px, py = points[idx]
|
|
263
|
+
if (pt[0] - px) ** 2 + (pt[1] - py) ** 2 < r2:
|
|
264
|
+
return False
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
first = (float(rng.random()), float(rng.random()))
|
|
268
|
+
points.append(first)
|
|
269
|
+
gx, gy = _grid_pos(first)
|
|
270
|
+
grid[gy, gx] = 0
|
|
271
|
+
active.append(0)
|
|
272
|
+
while active:
|
|
273
|
+
slot = int(rng.integers(0, len(active)))
|
|
274
|
+
idx = active[slot]
|
|
275
|
+
base = points[idx]
|
|
276
|
+
accepted = False
|
|
277
|
+
for _ in range(int(k)):
|
|
278
|
+
rr = radius * (1.0 + rng.random())
|
|
279
|
+
theta = 2.0 * np.pi * rng.random()
|
|
280
|
+
cand = (base[0] + rr * np.cos(theta), base[1] + rr * np.sin(theta))
|
|
281
|
+
if cand[0] < 0.0 or cand[0] > 1.0 or cand[1] < 0.0 or cand[1] > 1.0:
|
|
282
|
+
continue
|
|
283
|
+
if not _far_enough(cand):
|
|
284
|
+
continue
|
|
285
|
+
points.append((float(cand[0]), float(cand[1])))
|
|
286
|
+
new_idx = len(points) - 1
|
|
287
|
+
gx, gy = _grid_pos(cand)
|
|
288
|
+
grid[gy, gx] = new_idx
|
|
289
|
+
active.append(new_idx)
|
|
290
|
+
accepted = True
|
|
291
|
+
break
|
|
292
|
+
if not accepted:
|
|
293
|
+
active.pop(slot)
|
|
294
|
+
return np.asarray(points, dtype=float)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _bridson_density_cell(x, y, w, cfg, logger):
|
|
298
|
+
xlim = _axis_lim(x, cfg, "x")
|
|
299
|
+
ylim = _axis_lim(y, cfg, "y")
|
|
300
|
+
xscale = _axis_scale(cfg, "x")
|
|
301
|
+
yscale = _axis_scale(cfg, "y")
|
|
302
|
+
xp = _project_axis(x, xlim, xscale, "x")
|
|
303
|
+
yp = _project_axis(y, ylim, yscale, "y")
|
|
304
|
+
inside = np.isfinite(xp) & np.isfinite(yp) & (xp >= 0.0) & (xp <= 1.0) & (yp >= 0.0) & (yp <= 1.0)
|
|
305
|
+
if not np.any(inside):
|
|
306
|
+
raise ValueError("make_density_core bridson found no positive-mass samples inside the domain.")
|
|
307
|
+
if not np.all(inside):
|
|
308
|
+
_logger_emit(logger, "warning", f"make_density_core bridson ignored {int((~inside).sum())} samples outside the domain.")
|
|
309
|
+
bridson = cfg.get("bridson", {})
|
|
310
|
+
if not isinstance(bridson, Mapping):
|
|
311
|
+
bridson = {}
|
|
312
|
+
bin_value = int(bridson.get("bin", bridson.get("bins", cfg.get("bin", 32))))
|
|
313
|
+
if bin_value <= 0:
|
|
314
|
+
raise ValueError("make_density_core bridson bin must be positive.")
|
|
315
|
+
radius = 1.0 / float(bin_value)
|
|
316
|
+
seed = bridson.get("seed", cfg.get("seed", None))
|
|
317
|
+
seed = None if seed is None else int(seed)
|
|
318
|
+
k = int(bridson.get("k", bridson.get("candidates", 30)))
|
|
319
|
+
support = _bridson_points(radius, seed=seed, k=k)
|
|
320
|
+
samples = np.column_stack([xp[inside], yp[inside]])
|
|
321
|
+
try:
|
|
322
|
+
from scipy.spatial import cKDTree
|
|
323
|
+
|
|
324
|
+
_, idx = cKDTree(support).query(samples, k=1)
|
|
325
|
+
except Exception:
|
|
326
|
+
diff = samples[:, None, :] - support[None, :, :]
|
|
327
|
+
idx = np.argmin(np.sum(diff * diff, axis=2), axis=1)
|
|
328
|
+
mass = np.zeros(int(support.shape[0]), dtype=float)
|
|
329
|
+
np.add.at(mass, np.asarray(idx, dtype=np.int64), w[inside])
|
|
330
|
+
xs = _unproject_axis(support[:, 0], xlim, xscale)
|
|
331
|
+
ys = _unproject_axis(support[:, 1], ylim, yscale)
|
|
332
|
+
return xs, ys, mass
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _kde_core(x, y, w, cfg, logger):
|
|
336
|
+
xlim = _axis_lim(x, cfg, "x")
|
|
337
|
+
ylim = _axis_lim(y, cfg, "y")
|
|
338
|
+
xscale = _axis_scale(cfg, "x")
|
|
339
|
+
yscale = _axis_scale(cfg, "y")
|
|
340
|
+
if xscale != "linear" or yscale != "linear":
|
|
341
|
+
raise ValueError("make_density_core method=kde currently requires linear x/y scales.")
|
|
342
|
+
nx, ny = _grid_shape(cfg)
|
|
343
|
+
if x.shape[0] < 3:
|
|
344
|
+
raise ValueError("make_density_core method=kde requires at least three finite 2D samples.")
|
|
345
|
+
pts = np.vstack([x, y])
|
|
346
|
+
if np.linalg.matrix_rank(pts - pts.mean(axis=1, keepdims=True)) < 2:
|
|
347
|
+
raise ValueError("make_density_core method=kde requires non-collinear 2D samples.")
|
|
348
|
+
weights = _normalize(w, enabled=True)
|
|
349
|
+
try:
|
|
350
|
+
from scipy.stats import gaussian_kde
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise ImportError("make_density_core method=kde requires scipy.stats.gaussian_kde.") from e
|
|
353
|
+
kde = gaussian_kde(dataset=pts, bw_method=_bw_method(cfg), weights=weights)
|
|
354
|
+
xmin, xmax = xlim
|
|
355
|
+
ymin, ymax = ylim
|
|
356
|
+
dx = (xmax - xmin) / float(nx)
|
|
357
|
+
dy = (ymax - ymin) / float(ny)
|
|
358
|
+
x_centers = np.linspace(xmin + 0.5 * dx, xmax - 0.5 * dx, nx)
|
|
359
|
+
y_centers = np.linspace(ymin + 0.5 * dy, ymax - 0.5 * dy, ny)
|
|
360
|
+
xg, yg = np.meshgrid(x_centers, y_centers)
|
|
361
|
+
density = np.asarray(kde(np.vstack([xg.ravel(), yg.ravel()])), dtype=float).reshape(ny, nx)
|
|
362
|
+
density = np.maximum(np.where(np.isfinite(density), density, 0.0), 0.0)
|
|
363
|
+
mass = density.ravel() * dx * dy
|
|
364
|
+
return xg.ravel(), yg.ravel(), mass
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _diagnostics(*, method: str, input_count: int, output_count: int, normalize: bool, names: Tuple[str, str, str], total: float) -> str:
|
|
368
|
+
return (
|
|
369
|
+
"make_density_core diagnostics:\n"
|
|
370
|
+
f"\t method \t-> {method}\n"
|
|
371
|
+
f"\t input_samples \t-> {input_count}\n"
|
|
372
|
+
f"\t output_support \t-> {output_count}\n"
|
|
373
|
+
f"\t normalize \t-> {normalize}\n"
|
|
374
|
+
f"\t output_columns \t-> {names[0]}, {names[1]}, {names[2]}\n"
|
|
375
|
+
f"\t output_weight_sum \t-> {total:.17g}"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def density_cell(df: pd.DataFrame, cfg: Mapping[str, Any], logger=None) -> pd.DataFrame:
|
|
380
|
+
"""
|
|
381
|
+
Construct a minimal posterior density-support table from raw samples.
|
|
382
|
+
|
|
383
|
+
The output contains exactly three columns: support x, support y, and
|
|
384
|
+
aggregated posterior mass/weight. Geometry, density division, smoothing, and
|
|
385
|
+
plotting preparation are intentionally left to later transforms/rendering.
|
|
386
|
+
"""
|
|
387
|
+
if not isinstance(df, pd.DataFrame):
|
|
388
|
+
raise TypeError("make_density_core expects a pandas DataFrame.")
|
|
389
|
+
cfg = cfg if isinstance(cfg, Mapping) else {}
|
|
390
|
+
method = str(cfg.get("method", "grid")).strip().lower()
|
|
391
|
+
if method not in {"grid", "bridson", "kde"}:
|
|
392
|
+
raise ValueError("make_density_core method must be one of: grid, bridson, kde.")
|
|
393
|
+
memtrace_checkpoint(logger, "make_density_core.before", df)
|
|
394
|
+
x, y, w, input_count = _prepare_samples(df, cfg, logger)
|
|
395
|
+
if method == "grid":
|
|
396
|
+
xs, ys, mass = _grid_density_cell(x, y, w, cfg, logger)
|
|
397
|
+
elif method == "bridson":
|
|
398
|
+
xs, ys, mass = _bridson_density_cell(x, y, w, cfg, logger)
|
|
399
|
+
else:
|
|
400
|
+
xs, ys, mass = _kde_core(x, y, w, cfg, logger)
|
|
401
|
+
normalize = bool(cfg.get("normalize", True))
|
|
402
|
+
mass = _normalize(mass, enabled=normalize)
|
|
403
|
+
names = (
|
|
404
|
+
_output_name(cfg, "x", "x"),
|
|
405
|
+
_output_name(cfg, "y", "y"),
|
|
406
|
+
_output_name(cfg, "weight", "weight"),
|
|
407
|
+
)
|
|
408
|
+
out = pd.DataFrame({names[0]: np.asarray(xs, dtype=float), names[1]: np.asarray(ys, dtype=float), names[2]: mass})
|
|
409
|
+
if list(out.columns) != list(names):
|
|
410
|
+
out = out.loc[:, list(names)]
|
|
411
|
+
msg = _diagnostics(
|
|
412
|
+
method=method,
|
|
413
|
+
input_count=int(input_count),
|
|
414
|
+
output_count=int(out.shape[0]),
|
|
415
|
+
normalize=normalize,
|
|
416
|
+
names=names,
|
|
417
|
+
total=float(np.sum(out[names[2]].to_numpy(dtype=float))),
|
|
418
|
+
)
|
|
419
|
+
if bool(cfg.get("diagnostics", True)):
|
|
420
|
+
_logger_emit(logger, "warning", msg)
|
|
421
|
+
memtrace_checkpoint(
|
|
422
|
+
logger,
|
|
423
|
+
"make_density_core.after",
|
|
424
|
+
out,
|
|
425
|
+
extra={"method": method, "rows": int(out.shape[0]), "weight_sum": float(np.sum(out[names[2]]))},
|
|
426
|
+
)
|
|
427
|
+
return out
|