jarvisplot 1.3.10__tar.gz → 1.4.0__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.
Files changed (101) hide show
  1. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/PKG-INFO +1 -1
  2. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/SOURCES.txt +13 -0
  3. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/PKG-INFO +1 -1
  4. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/adapters_rect.py +9 -0
  5. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/density_cell_runtime.py +101 -15
  6. jarvisplot-1.4.0/jarvisplot/Figure/figure_types.py +597 -0
  7. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/interp_2d_runtime.py +60 -97
  8. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/interp_natural_neighbor_exact.py +3 -1
  9. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/method_registry.py +1 -0
  10. jarvisplot-1.4.0/jarvisplot/Figure/posterior_density_runtime.py +226 -0
  11. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/posterior_hpd.py +89 -0
  12. jarvisplot-1.4.0/jarvisplot/Figure/posterior_mesh.py +1416 -0
  13. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/preprocessor.py +50 -8
  14. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/preprocessor_runtime.py +25 -1
  15. jarvisplot-1.4.0/jarvisplot/__init__.py +3 -0
  16. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/args.json +8 -3
  17. jarvisplot-1.4.0/jarvisplot/cards/flowchart/default.json +46 -0
  18. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  19. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  20. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  21. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  22. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  23. jarvisplot-1.4.0/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  24. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/core.py +31 -0
  25. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/core_runtime.py +62 -10
  26. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/data_loader_runtime.py +14 -1
  27. jarvisplot-1.4.0/jarvisplot/flowchart.py +843 -0
  28. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/pyproject.toml +1 -1
  29. jarvisplot-1.4.0/tests/test_density_cell_transform.py +523 -0
  30. jarvisplot-1.4.0/tests/test_figure_type_expander.py +272 -0
  31. jarvisplot-1.4.0/tests/test_flowchart_renderer.py +182 -0
  32. jarvisplot-1.3.10/jarvisplot/utils/__init__.py +0 -0
  33. jarvisplot-1.3.10/tests/test_density_cell_transform.py +0 -147
  34. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  35. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  36. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/requires.txt +0 -0
  37. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/JarvisPLOT.egg-info/top_level.txt +0 -0
  38. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/README.md +0 -0
  39. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/__init__.py +0 -0
  40. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/adapters_ternary.py +0 -0
  41. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  42. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  43. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/config_runtime.py +0 -0
  44. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/data_pipelines.py +0 -0
  45. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  46. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/figure.py +0 -0
  47. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/helper.py +0 -0
  48. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  49. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/layer_runtime.py +0 -0
  50. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/layout_runtime.py +0 -0
  51. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/profile_runtime.py +0 -0
  52. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/Figure/style_runtime.py +0 -0
  53. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cache_store.py +0 -0
  54. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  55. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  56. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  57. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  58. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  59. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  60. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  61. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  62. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  63. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  64. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  65. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/colors/colormaps.json +0 -0
  66. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/default/output.json +0 -0
  67. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  68. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  69. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  70. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  71. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  72. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/icons/gambit.png +0 -0
  73. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/icons/gambit_small.png +0 -0
  74. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cards/style_preference.json +0 -0
  75. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/cli.py +0 -0
  76. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/client.py +0 -0
  77. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/config.py +0 -0
  78. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/core_assets.py +0 -0
  79. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/data_loader.py +0 -0
  80. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/data_loader_hdf5.py +0 -0
  81. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/data_loader_summary.py +0 -0
  82. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/inner_func.py +0 -0
  83. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/memtrace.py +0 -0
  84. {jarvisplot-1.3.10/jarvisplot → jarvisplot-1.4.0/jarvisplot/utils}/__init__.py +0 -0
  85. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/utils/cmaps.py +0 -0
  86. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/utils/dataframes.py +0 -0
  87. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/utils/expression.py +0 -0
  88. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/utils/interpolator.py +0 -0
  89. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/jarvisplot/utils/pathing.py +0 -0
  90. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/setup.cfg +0 -0
  91. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_data_loader_contracts.py +0 -0
  92. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_docs_status_labels.py +0 -0
  93. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_figure_contracts.py +0 -0
  94. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_interp_2d_transform.py +0 -0
  95. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_interp_natural_neighbor.py +0 -0
  96. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_interpolator_contracts.py +0 -0
  97. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_jpcontour_methods.py +0 -0
  98. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_parse_data_metadata.py +0 -0
  99. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_pathing_contracts.py +0 -0
  100. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/tests/test_posterior_hpd.py +0 -0
  101. {jarvisplot-1.3.10 → jarvisplot-1.4.0}/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.10
3
+ Version: 1.4.0
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -18,6 +18,7 @@ jarvisplot/data_loader.py
18
18
  jarvisplot/data_loader_hdf5.py
19
19
  jarvisplot/data_loader_runtime.py
20
20
  jarvisplot/data_loader_summary.py
21
+ jarvisplot/flowchart.py
21
22
  jarvisplot/inner_func.py
22
23
  jarvisplot/memtrace.py
23
24
  jarvisplot/Figure/__init__.py
@@ -29,6 +30,7 @@ jarvisplot/Figure/data_pipelines.py
29
30
  jarvisplot/Figure/density_cell_runtime.py
30
31
  jarvisplot/Figure/dynesty_runtime.py
31
32
  jarvisplot/Figure/figure.py
33
+ jarvisplot/Figure/figure_types.py
32
34
  jarvisplot/Figure/helper.py
33
35
  jarvisplot/Figure/interp_2d_runtime.py
34
36
  jarvisplot/Figure/interp_natural_neighbor.py
@@ -36,7 +38,9 @@ jarvisplot/Figure/interp_natural_neighbor_exact.py
36
38
  jarvisplot/Figure/layer_runtime.py
37
39
  jarvisplot/Figure/layout_runtime.py
38
40
  jarvisplot/Figure/method_registry.py
41
+ jarvisplot/Figure/posterior_density_runtime.py
39
42
  jarvisplot/Figure/posterior_hpd.py
43
+ jarvisplot/Figure/posterior_mesh.py
40
44
  jarvisplot/Figure/preprocessor.py
41
45
  jarvisplot/Figure/preprocessor_runtime.py
42
46
  jarvisplot/Figure/profile_runtime.py
@@ -57,6 +61,7 @@ jarvisplot/cards/a4paper/4x1/ternary.json
57
61
  jarvisplot/cards/a4paper/4x1/ternary_cmap.json
58
62
  jarvisplot/cards/colors/colormaps.json
59
63
  jarvisplot/cards/default/output.json
64
+ jarvisplot/cards/flowchart/default.json
60
65
  jarvisplot/cards/gambit/1x1/ternary.json
61
66
  jarvisplot/cards/gambit/2x1/rect_cmap.json
62
67
  jarvisplot/cards/gambit/2x1/ternary.json
@@ -64,6 +69,12 @@ jarvisplot/cards/gambit/2x1/ternary_cmap.json
64
69
  jarvisplot/cards/icons/JarvisHEP.png
65
70
  jarvisplot/cards/icons/gambit.png
66
71
  jarvisplot/cards/icons/gambit_small.png
72
+ jarvisplot/cards/icons/flowchart/JarvisHEP.png
73
+ jarvisplot/cards/icons/flowchart/calculator.png
74
+ jarvisplot/cards/icons/flowchart/inputfile.png
75
+ jarvisplot/cards/icons/flowchart/opera.png
76
+ jarvisplot/cards/icons/flowchart/outputfile.png
77
+ jarvisplot/cards/icons/flowchart/sampler.png
67
78
  jarvisplot/utils/__init__.py
68
79
  jarvisplot/utils/cmaps.py
69
80
  jarvisplot/utils/dataframes.py
@@ -74,6 +85,8 @@ tests/test_data_loader_contracts.py
74
85
  tests/test_density_cell_transform.py
75
86
  tests/test_docs_status_labels.py
76
87
  tests/test_figure_contracts.py
88
+ tests/test_figure_type_expander.py
89
+ tests/test_flowchart_renderer.py
77
90
  tests/test_interp_2d_transform.py
78
91
  tests/test_interp_natural_neighbor.py
79
92
  tests/test_interpolator_contracts.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 1.3.10
3
+ Version: 1.4.0
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -475,6 +475,15 @@ class StdAxesAdapter:
475
475
  artists = self.ax.plot(x, y, **kw)
476
476
  return _auto_clip(artists, self.ax, self._clip_path)
477
477
 
478
+ def quiver(self, **kwargs):
479
+ x = kwargs.pop("x")
480
+ y = kwargs.pop("y")
481
+ u = kwargs.pop("u")
482
+ v = kwargs.pop("v")
483
+ kw = self._merge("quiver", kwargs)
484
+ artists = self.ax.quiver(x, y, u, v, **kw)
485
+ return _auto_clip(artists, self.ax, self._clip_path)
486
+
478
487
  def fill(self, **kwargs):
479
488
  x, y = kwargs.pop("x"), kwargs.pop("y")
480
489
  kw = self._merge("fill", kwargs)
@@ -7,7 +7,9 @@ import numpy as np
7
7
  import pandas as pd
8
8
 
9
9
  from ..memtrace import memtrace_checkpoint
10
+ from ..utils.pathing import resolve_project_path
10
11
  from ..utils.expression import eval_dataframe_expression
12
+ from .posterior_mesh import build_posterior_mesh, conservative_density, mesh_diagnostics_frame
11
13
 
12
14
 
13
15
  _DENSITY_CELL_TYPES = {"make_density_core"}
@@ -67,7 +69,11 @@ def _coord_cfg(cfg: Mapping[str, Any], key: str) -> Mapping[str, Any]:
67
69
 
68
70
 
69
71
  def _output_name(cfg: Mapping[str, Any], key: str, default: str) -> str:
70
- name = _coord_cfg(cfg, key).get("name", default)
72
+ output = cfg.get("output", {})
73
+ if isinstance(output, Mapping) and output.get(key) is not None:
74
+ name = output.get(key, default)
75
+ else:
76
+ name = _coord_cfg(cfg, key).get("name", default)
71
77
  text = str(name).strip()
72
78
  return text or default
73
79
 
@@ -144,7 +150,7 @@ def _unproject_axis(values: np.ndarray, lim: Tuple[float, float], scale: str) ->
144
150
  return lo + values * (hi - lo)
145
151
 
146
152
 
147
- def _grid_shape(cfg: Mapping[str, Any], default: int = 128) -> Tuple[int, int]:
153
+ def _grid_shape(cfg: Mapping[str, Any], default: int = 64) -> Tuple[int, int]:
148
154
  grid = cfg.get("grid", {})
149
155
  if not isinstance(grid, Mapping):
150
156
  grid = {}
@@ -309,7 +315,7 @@ def _bridson_density_cell(x, y, w, cfg, logger):
309
315
  bridson = cfg.get("bridson", {})
310
316
  if not isinstance(bridson, Mapping):
311
317
  bridson = {}
312
- bin_value = int(bridson.get("bin", bridson.get("bins", cfg.get("bin", 32))))
318
+ bin_value = int(bridson.get("bin", bridson.get("bins", cfg.get("bin", cfg.get("bins", 64)))))
313
319
  if bin_value <= 0:
314
320
  raise ValueError("make_density_core bridson bin must be positive.")
315
321
  radius = 1.0 / float(bin_value)
@@ -318,20 +324,31 @@ def _bridson_density_cell(x, y, w, cfg, logger):
318
324
  k = int(bridson.get("k", bridson.get("candidates", 30)))
319
325
  support = _bridson_points(radius, seed=seed, k=k)
320
326
  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)
327
+ mesh_cfg = cfg.get("_mesh_debug", {})
328
+ if not isinstance(mesh_cfg, Mapping):
329
+ mesh_cfg = {}
330
+ refinement_cfg = cfg.get("refinement", mesh_cfg.get("refinement", None))
331
+ mesh = build_posterior_mesh(
332
+ support,
333
+ samples,
334
+ w[inside],
335
+ xlim=(0.0, 1.0),
336
+ ylim=(0.0, 1.0),
337
+ include_polygons=bool(mesh_cfg.get("include_polygons", False)),
338
+ strict_geometry=False,
339
+ geometry_space="normalized",
340
+ refinement=refinement_cfg if isinstance(refinement_cfg, Mapping) else None,
341
+ )
342
+ _bridson_density_cell.last_mesh = mesh
343
+ mass = mesh.masses
344
+ xs = _unproject_axis(mesh.generators[:, 0], xlim, xscale)
345
+ ys = _unproject_axis(mesh.generators[:, 1], ylim, yscale)
332
346
  return xs, ys, mass
333
347
 
334
348
 
349
+ _bridson_density_cell.last_mesh = None # type: ignore[attr-defined]
350
+
351
+
335
352
  def _kde_core(x, y, w, cfg, logger):
336
353
  xlim = _axis_lim(x, cfg, "x")
337
354
  ylim = _axis_lim(y, cfg, "y")
@@ -376,6 +393,60 @@ def _diagnostics(*, method: str, input_count: int, output_count: int, normalize:
376
393
  )
377
394
 
378
395
 
396
+ def _mesh_diagnostics(mesh) -> str:
397
+ if mesh is None:
398
+ return ""
399
+ diag = getattr(mesh, "diagnostics", {}) or {}
400
+ return (
401
+ "\nposterior mesh diagnostics:\n"
402
+ f"\t generators \t-> {int(diag.get('generator_count', 0))}\n"
403
+ f"\t nonzero_mass \t-> {int(diag.get('nonzero_mass_generators', 0))}\n"
404
+ f"\t empty_generators -> {int(diag.get('empty_generators', 0))}\n"
405
+ f"\t area_method \t-> {diag.get('area_method', 'not_performed')}\n"
406
+ f"\t geometry_space \t-> {diag.get('geometry_space', 'unknown')}\n"
407
+ f"\t max_centroid_drift -> {float(diag.get('max_centroid_drift', float('nan'))):.17g}\n"
408
+ f"\t max_anisotropy \t-> {float(diag.get('max_anisotropy_ratio', float('nan'))):.17g}\n"
409
+ f"\t mass_vs_geometry_corr -> {float(diag.get('mass_geometry_stress_corr', float('nan'))):.17g}\n"
410
+ f"\t refinement_enabled -> {bool(diag.get('refinement_enabled', False))}\n"
411
+ f"\t refinement_iters -> {int(diag.get('refinement_iterations_completed', 0))}\n"
412
+ f"\t refinement_splits -> {int(diag.get('refinement_splits', 0))}\n"
413
+ f"\t refinement_merges -> {int(diag.get('refinement_merges', 0))}"
414
+ )
415
+
416
+
417
+ def _export_mesh_debug(mesh, cfg: Mapping[str, Any], xs: np.ndarray, ys: np.ndarray, logger) -> None:
418
+ if mesh is None:
419
+ return
420
+ mesh_cfg = cfg.get("_mesh_debug", {})
421
+ if not isinstance(mesh_cfg, Mapping):
422
+ return
423
+ target = mesh_cfg.get("to_csv", mesh_cfg.get("path", None))
424
+ if target is None:
425
+ return
426
+ path = str(target).strip()
427
+ if not path:
428
+ return
429
+ base_dir = cfg.get("_base_dir", None)
430
+ out_path = resolve_project_path(path, base_dir=base_dir)
431
+ out_path.parent.mkdir(parents=True, exist_ok=True)
432
+ frame = mesh_diagnostics_frame(mesh, x=xs, y=ys)
433
+ try:
434
+ xlim = _axis_lim(np.asarray(xs, dtype=float), cfg, "x")
435
+ ylim = _axis_lim(np.asarray(ys, dtype=float), cfg, "y")
436
+ xscale = _axis_scale(cfg, "x")
437
+ yscale = _axis_scale(cfg, "y")
438
+ centroid_x = _unproject_axis(mesh.centroid_proposal[:, 0], xlim, xscale)
439
+ centroid_y = _unproject_axis(mesh.centroid_proposal[:, 1], ylim, yscale)
440
+ frame["centroid_x"] = centroid_x
441
+ frame["centroid_y"] = centroid_y
442
+ frame["drift_x"] = centroid_x - np.asarray(xs, dtype=float)
443
+ frame["drift_y"] = centroid_y - np.asarray(ys, dtype=float)
444
+ except Exception:
445
+ pass
446
+ frame.to_csv(out_path, index=False)
447
+ _logger_emit(logger, "warning", f"posterior mesh diagnostics exported -> {out_path}")
448
+
449
+
379
450
  def density_cell(df: pd.DataFrame, cfg: Mapping[str, Any], logger=None) -> pd.DataFrame:
380
451
  """
381
452
  Construct a minimal posterior density-support table from raw samples.
@@ -387,19 +458,29 @@ def density_cell(df: pd.DataFrame, cfg: Mapping[str, Any], logger=None) -> pd.Da
387
458
  if not isinstance(df, pd.DataFrame):
388
459
  raise TypeError("make_density_core expects a pandas DataFrame.")
389
460
  cfg = cfg if isinstance(cfg, Mapping) else {}
390
- method = str(cfg.get("method", "grid")).strip().lower()
461
+ method = str(cfg.get("method", "bridson")).strip().lower()
391
462
  if method not in {"grid", "bridson", "kde"}:
392
463
  raise ValueError("make_density_core method must be one of: grid, bridson, kde.")
393
464
  memtrace_checkpoint(logger, "make_density_core.before", df)
465
+ density_cell.last_mesh = None
394
466
  x, y, w, input_count = _prepare_samples(df, cfg, logger)
395
467
  if method == "grid":
396
468
  xs, ys, mass = _grid_density_cell(x, y, w, cfg, logger)
397
469
  elif method == "bridson":
398
470
  xs, ys, mass = _bridson_density_cell(x, y, w, cfg, logger)
471
+ density_cell.last_mesh = _bridson_density_cell.last_mesh
399
472
  else:
400
473
  xs, ys, mass = _kde_core(x, y, w, cfg, logger)
401
474
  normalize = bool(cfg.get("normalize", True))
402
475
  mass = _normalize(mass, enabled=normalize)
476
+ if density_cell.last_mesh is not None:
477
+ density_cell.last_mesh.masses = np.asarray(mass, dtype=float)
478
+ density_cell.last_mesh.densities = conservative_density(
479
+ density_cell.last_mesh.masses,
480
+ density_cell.last_mesh.areas,
481
+ )
482
+ density_cell.last_mesh.diagnostics["mass_sum"] = float(np.sum(density_cell.last_mesh.masses))
483
+ _export_mesh_debug(density_cell.last_mesh, cfg, xs, ys, logger)
403
484
  names = (
404
485
  _output_name(cfg, "x", "x"),
405
486
  _output_name(cfg, "y", "y"),
@@ -417,6 +498,8 @@ def density_cell(df: pd.DataFrame, cfg: Mapping[str, Any], logger=None) -> pd.Da
417
498
  total=float(np.sum(out[names[2]].to_numpy(dtype=float))),
418
499
  )
419
500
  if bool(cfg.get("diagnostics", True)):
501
+ if method == "bridson":
502
+ msg += _mesh_diagnostics(density_cell.last_mesh)
420
503
  _logger_emit(logger, "warning", msg)
421
504
  memtrace_checkpoint(
422
505
  logger,
@@ -425,3 +508,6 @@ def density_cell(df: pd.DataFrame, cfg: Mapping[str, Any], logger=None) -> pd.Da
425
508
  extra={"method": method, "rows": int(out.shape[0]), "weight_sum": float(np.sum(out[names[2]]))},
426
509
  )
427
510
  return out
511
+
512
+
513
+ density_cell.last_mesh = None # type: ignore[attr-defined]