jarvisplot 2.1.5__tar.gz → 2.1.6__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 (254) hide show
  1. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/PKG-INFO +12 -3
  2. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/SOURCES.txt +6 -0
  3. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/PKG-INFO +12 -3
  4. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/README.md +11 -2
  5. jarvisplot-2.1.6/jarvisplot/Figure/adaptive_cdf_runtime.py +131 -0
  6. jarvisplot-2.1.6/jarvisplot/Figure/distribution_1d_runtime.py +165 -0
  7. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/preprocessor.py +15 -0
  8. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/preprocessor_runtime.py +6 -0
  9. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/column_demand.py +10 -0
  10. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_loader.py +6 -0
  11. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_loader_runtime.py +6 -1
  12. jarvisplot-2.1.6/jarvisplot/distribution_1d_config.py +167 -0
  13. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/dryrun_runtime.py +12 -3
  14. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/man_transforms.py +8 -5
  15. jarvisplot-2.1.6/jarvisplot/schema/core/distribution-1d.json +72 -0
  16. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/transform.json +8 -0
  17. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/manifest.json +3 -0
  18. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/transform_contracts.py +67 -0
  19. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/pyproject.toml +1 -1
  20. jarvisplot-2.1.6/tests/test_adaptive_cdf.py +141 -0
  21. jarvisplot-2.1.6/tests/test_distribution_1d_transform.py +234 -0
  22. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  23. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  24. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/requires.txt +0 -0
  25. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/top_level.txt +0 -0
  26. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/__init__.py +0 -0
  27. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/adapters_rect.py +0 -0
  28. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/adapters_ternary.py +0 -0
  29. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
  30. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  31. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  32. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/config_runtime.py +0 -0
  33. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_layout.py +0 -0
  34. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
  35. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_order.py +0 -0
  36. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/correlation_runtime.py +0 -0
  37. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/corrplot_runtime.py +0 -0
  38. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/data_pipelines.py +0 -0
  39. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/debug_config.py +0 -0
  40. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  41. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/design_runtime.py +0 -0
  42. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  43. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/figure.py +0 -0
  44. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/figure_types.py +0 -0
  45. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/helper.py +0 -0
  46. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  47. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  48. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  49. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/layer_runtime.py +0 -0
  50. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/layout_runtime.py +0 -0
  51. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/method_registry.py +0 -0
  52. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  53. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_hpd.py +0 -0
  54. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_mesh.py +0 -0
  55. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/profile_runtime.py +0 -0
  56. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/significance_runtime.py +0 -0
  57. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/Figure/style_runtime.py +0 -0
  58. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/__init__.py +0 -0
  59. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/agent_digest.py +0 -0
  60. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/agent_io.py +0 -0
  61. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cache_store.py +0 -0
  62. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/capabilities.py +0 -0
  63. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  64. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  65. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  66. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  67. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  68. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +0 -0
  69. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rectratio.json +0 -0
  70. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  71. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  72. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  73. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  74. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  75. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  76. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/args.json +0 -0
  77. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/colors/colormaps.json +0 -0
  78. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/corrplot/diamond.json +0 -0
  79. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/corrplot/matrix.json +0 -0
  80. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/default/output.json +0 -0
  81. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/flowchart/default.json +0 -0
  82. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  83. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  84. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  85. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  86. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  87. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  88. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  89. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  90. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  91. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  92. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  93. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/gambit.png +0 -0
  94. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/icons/gambit_small.png +0 -0
  95. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cards/style_preference.json +0 -0
  96. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cli.py +0 -0
  97. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/cli_help.py +0 -0
  98. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/client.py +0 -0
  99. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/column_probe.py +0 -0
  100. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/config.py +0 -0
  101. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/config_address.py +0 -0
  102. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/core.py +0 -0
  103. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/core_assets.py +0 -0
  104. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/core_runtime.py +0 -0
  105. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_access.py +0 -0
  106. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_generation_contracts.py +0 -0
  107. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_loader_hdf5.py +0 -0
  108. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/data_loader_summary.py +0 -0
  109. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/dataset_types.py +0 -0
  110. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/diagnostic_guidance.py +0 -0
  111. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/diagnostics.py +0 -0
  112. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/expr_names.py +0 -0
  113. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/fix_apply.py +0 -0
  114. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/flowchart.py +0 -0
  115. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/generated_data.py +0 -0
  116. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/inner_func.py +0 -0
  117. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/man_catalog.py +0 -0
  118. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/man_methods.py +0 -0
  119. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/man_render_agent.py +0 -0
  120. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/man_render_human.py +0 -0
  121. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  122. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  123. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  124. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
  125. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  126. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  127. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/field-interp.yaml +0 -0
  128. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/generated-data.yaml +0 -0
  129. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/layer-method.yaml +0 -0
  130. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/manifest.yaml +0 -0
  131. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/method_notes.yaml +0 -0
  132. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/style-axes.yaml +0 -0
  133. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
  134. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  135. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
  136. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/validate.yaml +0 -0
  137. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/workflow.yaml +0 -0
  138. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
  139. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/memtrace.py +0 -0
  140. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/method_contracts.py +0 -0
  141. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/render_health.py +0 -0
  142. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/common.json +0 -0
  143. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/dataset.json +0 -0
  144. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/figure.json +0 -0
  145. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/generated-data.json +0 -0
  146. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/core/layer.json +0 -0
  147. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/bar.json +0 -0
  148. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/barh.json +0 -0
  149. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/contour.json +0 -0
  150. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/contourf.json +0 -0
  151. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/corrplot.json +0 -0
  152. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  153. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/errorbar.json +0 -0
  154. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill.json +0 -0
  155. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill_between.json +0 -0
  156. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  157. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/hist.json +0 -0
  158. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/hist2d.json +0 -0
  159. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/imshow.json +0 -0
  160. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpcontour.json +0 -0
  161. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  162. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpfield.json +0 -0
  163. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/pcolor.json +0 -0
  164. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  165. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/plot.json +0 -0
  166. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/quiver.json +0 -0
  167. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/scatter.json +0 -0
  168. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/stairs.json +0 -0
  169. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/step.json +0 -0
  170. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tricontour.json +0 -0
  171. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tricontourf.json +0 -0
  172. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tripcolor.json +0 -0
  173. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  174. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/triplot.json +0 -0
  175. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/voronoi.json +0 -0
  176. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/methods/voronoif.json +0 -0
  177. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema/plot-config.json +0 -0
  178. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema_catalog.py +0 -0
  179. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/schema_diagnostics.py +0 -0
  180. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/templates_catalog.py +0 -0
  181. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/__init__.py +0 -0
  182. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/cmaps.py +0 -0
  183. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/dataframes.py +0 -0
  184. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/expression.py +0 -0
  185. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/interpolator.py +0 -0
  186. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/utils/pathing.py +0 -0
  187. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/validation.py +0 -0
  188. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/__init__.py +0 -0
  189. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/cap.py +0 -0
  190. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/config_cmd.py +0 -0
  191. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/data.py +0 -0
  192. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/doctor.py +0 -0
  193. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/dryrun.py +0 -0
  194. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/explain.py +0 -0
  195. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/man.py +0 -0
  196. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/suggest.py +0 -0
  197. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/template.py +0 -0
  198. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/verbs/validate.py +0 -0
  199. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/jarvisplot/yaml_io.py +0 -0
  200. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/setup.cfg +0 -0
  201. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_agent_digest.py +0 -0
  202. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_agent_envelope.py +0 -0
  203. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_axis_title.py +0 -0
  204. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_capabilities.py +0 -0
  205. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_card_debug_shape.py +0 -0
  206. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_cli_help.py +0 -0
  207. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_cli_verbs.py +0 -0
  208. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_cmaps.py +0 -0
  209. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_column_existence.py +0 -0
  210. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_config_addressing.py +0 -0
  211. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_config_edit.py +0 -0
  212. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_config_expand.py +0 -0
  213. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_correlation_runtime.py +0 -0
  214. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_corrplot.py +0 -0
  215. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_data_access.py +0 -0
  216. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_data_describe.py +0 -0
  217. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_data_head_eval.py +0 -0
  218. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_data_loader_contracts.py +0 -0
  219. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_data_pipelines.py +0 -0
  220. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_debug_config.py +0 -0
  221. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_debug_switches.py +0 -0
  222. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_debug_yaml_override.py +0 -0
  223. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_density_cell_transform.py +0 -0
  224. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_design_runtime.py +0 -0
  225. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_diagnostic_guidance.py +0 -0
  226. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_docs_status_labels.py +0 -0
  227. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_doctor_partial.py +0 -0
  228. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_explain.py +0 -0
  229. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_figure_contracts.py +0 -0
  230. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_figure_type_expander.py +0 -0
  231. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_fix_application.py +0 -0
  232. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_flowchart_renderer.py +0 -0
  233. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_generated_data.py +0 -0
  234. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_imshow_adapter.py +0 -0
  235. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_interp_2d_transform.py +0 -0
  236. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_interp_natural_neighbor.py +0 -0
  237. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_interpolator_contracts.py +0 -0
  238. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_jpcontour_methods.py +0 -0
  239. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_man_cli.py +0 -0
  240. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_method_contracts.py +0 -0
  241. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_parse_data_metadata.py +0 -0
  242. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_pathing_contracts.py +0 -0
  243. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_posterior_hpd.py +0 -0
  244. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_profile_runtime.py +0 -0
  245. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_render_health.py +0 -0
  246. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_row_ledger.py +0 -0
  247. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_schema_catalog.py +0 -0
  248. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_suggest.py +0 -0
  249. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_suggest_axes.py +0 -0
  250. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_template_catalog.py +0 -0
  251. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_template_json.py +0 -0
  252. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_ternary_debug.py +0 -0
  253. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_transform_contracts.py +0 -0
  254. {jarvisplot-2.1.5 → jarvisplot-2.1.6}/tests/test_validate_no_matplotlib.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.5
3
+ Version: 2.1.6
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -161,11 +161,20 @@ The loop `jplot man` prints is:
161
161
  5. `jplot <file>` — render, only when the figure itself is needed.
162
162
 
163
163
  `jplot man --json` is the machine-readable manual: 15 topics, plus a
164
- live page for every one of the 28 layer methods and 11 transform steps
165
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
164
+ live pages for every registered layer method and transform step
165
+ (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
166
166
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
167
167
  code.
168
168
 
169
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
170
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
171
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
172
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
173
+ [runnable example](Example/pdf_cdf_1d.yaml).
174
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
175
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
176
+ to reproduce the original all-node reconstruction.
177
+
169
178
  Two things worth knowing before trusting a config:
170
179
 
171
180
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -30,6 +30,7 @@ jarvisplot/data_loader_summary.py
30
30
  jarvisplot/dataset_types.py
31
31
  jarvisplot/diagnostic_guidance.py
32
32
  jarvisplot/diagnostics.py
33
+ jarvisplot/distribution_1d_config.py
33
34
  jarvisplot/dryrun_runtime.py
34
35
  jarvisplot/expr_names.py
35
36
  jarvisplot/fix_apply.py
@@ -53,6 +54,7 @@ jarvisplot/yaml_io.py
53
54
  jarvisplot/Figure/__init__.py
54
55
  jarvisplot/Figure/adapters_rect.py
55
56
  jarvisplot/Figure/adapters_ternary.py
57
+ jarvisplot/Figure/adaptive_cdf_runtime.py
56
58
  jarvisplot/Figure/bin_stat_runtime.py
57
59
  jarvisplot/Figure/colorbar_runtime.py
58
60
  jarvisplot/Figure/config_runtime.py
@@ -65,6 +67,7 @@ jarvisplot/Figure/data_pipelines.py
65
67
  jarvisplot/Figure/debug_config.py
66
68
  jarvisplot/Figure/density_cell_runtime.py
67
69
  jarvisplot/Figure/design_runtime.py
70
+ jarvisplot/Figure/distribution_1d_runtime.py
68
71
  jarvisplot/Figure/dynesty_runtime.py
69
72
  jarvisplot/Figure/figure.py
70
73
  jarvisplot/Figure/figure_types.py
@@ -139,6 +142,7 @@ jarvisplot/schema/manifest.json
139
142
  jarvisplot/schema/plot-config.json
140
143
  jarvisplot/schema/core/common.json
141
144
  jarvisplot/schema/core/dataset.json
145
+ jarvisplot/schema/core/distribution-1d.json
142
146
  jarvisplot/schema/core/figure.json
143
147
  jarvisplot/schema/core/generated-data.json
144
148
  jarvisplot/schema/core/layer.json
@@ -190,6 +194,7 @@ jarvisplot/verbs/man.py
190
194
  jarvisplot/verbs/suggest.py
191
195
  jarvisplot/verbs/template.py
192
196
  jarvisplot/verbs/validate.py
197
+ tests/test_adaptive_cdf.py
193
198
  tests/test_agent_digest.py
194
199
  tests/test_agent_envelope.py
195
200
  tests/test_axis_title.py
@@ -215,6 +220,7 @@ tests/test_debug_yaml_override.py
215
220
  tests/test_density_cell_transform.py
216
221
  tests/test_design_runtime.py
217
222
  tests/test_diagnostic_guidance.py
223
+ tests/test_distribution_1d_transform.py
218
224
  tests/test_docs_status_labels.py
219
225
  tests/test_doctor_partial.py
220
226
  tests/test_explain.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.5
3
+ Version: 2.1.6
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -161,11 +161,20 @@ The loop `jplot man` prints is:
161
161
  5. `jplot <file>` — render, only when the figure itself is needed.
162
162
 
163
163
  `jplot man --json` is the machine-readable manual: 15 topics, plus a
164
- live page for every one of the 28 layer methods and 11 transform steps
165
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
164
+ live pages for every registered layer method and transform step
165
+ (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
166
166
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
167
167
  code.
168
168
 
169
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
170
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
171
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
172
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
173
+ [runnable example](Example/pdf_cdf_1d.yaml).
174
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
175
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
176
+ to reproduce the original all-node reconstruction.
177
+
169
178
  Two things worth knowing before trusting a config:
170
179
 
171
180
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -139,11 +139,20 @@ The loop `jplot man` prints is:
139
139
  5. `jplot <file>` — render, only when the figure itself is needed.
140
140
 
141
141
  `jplot man --json` is the machine-readable manual: 15 topics, plus a
142
- live page for every one of the 28 layer methods and 11 transform steps
143
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
142
+ live pages for every registered layer method and transform step
143
+ (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
144
144
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
145
145
  code.
146
146
 
147
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
148
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
149
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
150
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
151
+ [runnable example](Example/pdf_cdf_1d.yaml).
152
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
153
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
154
+ to reproduce the original all-node reconstruction.
155
+
147
156
  Two things worth knowing before trusting a config:
148
157
 
149
158
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -0,0 +1,131 @@
1
+ """Adaptive empirical-CDF anchors and a local, monotone C2 quintic interpolant.
2
+
3
+ No bins, kernels, or nonlinear solver. The quartic PDF is represented in the
4
+ Bernstein basis; nonnegative coefficients certify nonnegativity on each whole
5
+ interval, rather than only at sampled output points.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from heapq import heappop, heappush
11
+
12
+ import numpy as np
13
+ from scipy.interpolate import BPoly, CubicSpline, PchipInterpolator
14
+
15
+ from ..distribution_1d_config import AnchorSettings
16
+
17
+
18
+ def monotone_c2(x: np.ndarray, y: np.ndarray) -> BPoly:
19
+ """Exact C2 Hermite quintics with locally limited slopes and curvatures.
20
+
21
+ On an interval of width h and secant m, the quartic derivative's Bernstein
22
+ coefficients are [d0, d0+h*a0/4, 5*m-2*(d0+d1)+h*(a1-a0)/4,
23
+ d1-h*a1/4, d1]. Limit curvatures first, then shrink each node's (d,a)
24
+ together so all five coefficients are nonnegative. Each shared node has
25
+ one value, slope, and curvature: the assembled CDF is C2 and PDF is C1.
26
+ The support endpoints have zero slope/curvature, allowing constant CDF
27
+ extension and zero PDF extension without a boundary derivative jump.
28
+ """
29
+ x, y = np.asarray(x, dtype=float), np.asarray(y, dtype=float)
30
+ if x.ndim != 1 or len(x) < 2 or y.shape != x.shape or not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)):
31
+ raise ValueError("monotone_c2 needs matching finite 1D anchor arrays")
32
+ h, mass = np.diff(x), np.diff(y)
33
+ if np.any(h <= 0) or np.any(mass < 0):
34
+ raise ValueError("monotone_c2 requires strictly increasing x and nondecreasing CDF anchors")
35
+ # A clamped cubic estimates smooth slopes/curvatures in linear time. Its
36
+ # unconstrained interpolant is never returned: the quintic limiter below
37
+ # certifies monotonicity even where the cubic estimate would overshoot.
38
+ seed = CubicSpline(x, y, bc_type=((1, 0.0), (1, 0.0)))
39
+ slopes = np.maximum(seed(x, 1), 0.0)
40
+ slopes[[0, -1]] = 0.0
41
+ curvature = seed(x, 2)
42
+ # Adjacent interval constraints for the first/last PDF control coefficients.
43
+ lower, upper = np.full(len(x), -np.inf), np.full(len(x), np.inf)
44
+ lower[:-1] = -4 * slopes[:-1] / h
45
+ upper[1:] = 4 * slopes[1:] / h
46
+ curvature = np.clip(curvature, lower, upper)
47
+ curvature[[0, -1]] = 0.0
48
+ # Both contributions are nonnegative after clipping. Reducing either node
49
+ # cannot violate another interval's constraint, so one local pass suffices.
50
+ left = 2 * slopes[:-1] + h * curvature[:-1] / 4
51
+ right = 2 * slopes[1:] - h * curvature[1:] / 4
52
+ available = 5 * mass / h
53
+ total = left + right
54
+ factors = np.ones(len(h))
55
+ active = total > available
56
+ factors[active] = (available[active] / total[active]) * (1 - 32 * np.finfo(float).eps)
57
+ node_factors = np.ones(len(x))
58
+ node_factors[:-1] = np.minimum(node_factors[:-1], factors)
59
+ node_factors[1:] = np.minimum(node_factors[1:], factors)
60
+ slopes *= node_factors
61
+ curvature *= node_factors
62
+ if not np.all(np.isfinite(slopes)) or not np.all(np.isfinite(curvature)):
63
+ raise ValueError("monotone_c2 cannot resolve this anchor spacing; inspect support and score precision")
64
+ return BPoly.from_derivatives(x, np.column_stack((y, slopes, curvature)), extrapolate=False)
65
+
66
+
67
+ def interpolate_cdf(x: np.ndarray, y: np.ndarray, method: str):
68
+ if method == "monotone_c2":
69
+ return monotone_c2(x, y)
70
+ if method == "pchip":
71
+ return PchipInterpolator(x, y, extrapolate=False)
72
+ raise ValueError(f"unknown CDF interpolation {method!r}")
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class AnchorFit:
77
+ curve: BPoly | PchipInterpolator
78
+ indices: np.ndarray
79
+ max_error: float
80
+ stop_reason: str
81
+
82
+
83
+ def fit_anchors(x: np.ndarray, y: np.ndarray, config: AnchorSettings, interpolation: str) -> AnchorFit:
84
+ """Select only real empirical nodes plus the supplied support endpoints.
85
+
86
+ A bounded set of probability seeds starts the fit. Each pass evaluates
87
+ residuals against all empirical knots once, then scans disjoint intervals
88
+ and refines up to eight worst intervals. min_mass is the required observed
89
+ cumulative mass on EACH side of a proposed new node; it stops the selector
90
+ from chasing individual sample jumps. No output grid participates.
91
+ """
92
+ if config.method == "all":
93
+ curve = interpolate_cdf(x, y, interpolation)
94
+ return AnchorFit(curve, np.arange(len(x)), 0.0, "all")
95
+ # Retain the first empirical value and the first empirical value reaching
96
+ # probability one, so the fit preserves the observed onset and upper tail.
97
+ last = min(int(np.searchsorted(y, 1.0)), len(x) - 1)
98
+ selected = {0, min(1, len(x) - 1), last, len(x) - 1}
99
+ seed_count = min(9, config.max_points - len(selected) + 1)
100
+ for target in np.linspace(0, 1, seed_count)[1:-1]:
101
+ index = min(int(np.searchsorted(y, target)), len(x) - 1)
102
+ ordered = sorted(selected)
103
+ slot = int(np.searchsorted(ordered, index))
104
+ if index in selected or slot == 0 or slot == len(ordered):
105
+ continue
106
+ if min(y[index] - y[ordered[slot - 1]], y[ordered[slot]] - y[index]) >= config.min_mass:
107
+ selected.add(index)
108
+ while True:
109
+ indices = np.array(sorted(selected), dtype=int)
110
+ curve = interpolate_cdf(x[indices], y[indices], interpolation)
111
+ residual = np.abs(curve(x) - y)
112
+ error = float(np.max(residual))
113
+ if error <= config.tolerance:
114
+ return AnchorFit(curve, indices, error, "tolerance")
115
+ if len(selected) >= config.max_points:
116
+ return AnchorFit(curve, indices, error, "max_points")
117
+ queue = []
118
+ for lo, hi in zip(indices[:-1], indices[1:]):
119
+ # Search only nodes with enough probability mass on both sides.
120
+ start = max(lo + 1, int(np.searchsorted(y, y[lo] + config.min_mass, side="left")))
121
+ end = min(hi, int(np.searchsorted(y, y[hi] - config.min_mass, side="right")))
122
+ if start >= end:
123
+ continue
124
+ index = start + int(np.argmax(residual[start:end]))
125
+ if residual[index] > config.tolerance:
126
+ heappush(queue, (-float(residual[index]), index))
127
+ if not queue:
128
+ return AnchorFit(curve, indices, error, "min_mass")
129
+ for _ in range(min(8, config.max_points - len(selected), len(queue))):
130
+ _, index = heappop(queue)
131
+ selected.add(index)
@@ -0,0 +1,165 @@
1
+ """Raw weighted samples -> continuous CDF, optionally its analytic derivative.
2
+
3
+ PDF1D and CDF1D share the reconstruction. No histogram or sample-grid
4
+ renormalisation participates in fitting: the grid only evaluates the interpolant.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Mapping
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ from scipy.interpolate import PchipInterpolator
13
+
14
+ from ..distribution_1d_config import KINDS, settings
15
+ from ..memtrace import memtrace_checkpoint, memtrace_object_inventory
16
+ from ..utils.expression import eval_dataframe_expression
17
+ from .adaptive_cdf_runtime import fit_anchors
18
+
19
+ ALGORITHM_REVISION = "weighted-ecdf-adaptive-bernstein-c2-v3"
20
+
21
+
22
+ def _values(df: pd.DataFrame, expr: Any, logger=None) -> np.ndarray:
23
+ values = np.asarray(eval_dataframe_expression(df, expr, logger=logger), dtype=float)
24
+ if values.ndim == 0:
25
+ return np.full(len(df), float(values))
26
+ if values.shape != (len(df),):
27
+ raise ValueError(f"expression {expr!r} must return one value per input row, got {values.shape}")
28
+ return values
29
+
30
+
31
+ def _infer_limits(x: np.ndarray, scale: str) -> tuple[float, float]:
32
+ unique = np.unique(x)
33
+ if len(unique) < 2:
34
+ raise ValueError("one unique sample cannot define a continuous support; set coordinates.x.lim")
35
+ lo = unique[0] - 0.5 * (unique[1] - unique[0])
36
+ hi = unique[-1] + 0.5 * (unique[-1] - unique[-2])
37
+ if scale == "log":
38
+ if unique[0] <= 0:
39
+ raise ValueError("coordinates.x.scale: log requires positive samples")
40
+ lo = max(lo, unique[0] * 0.5)
41
+ if not np.isfinite(lo) or not np.isfinite(hi) or not lo < hi:
42
+ raise ValueError("cannot infer finite support; set coordinates.x.lim")
43
+ return float(lo), float(hi)
44
+
45
+
46
+ def empirical_knots(x: np.ndarray, weights: np.ndarray, limits: tuple[float, float]):
47
+ """Inclusive empirical nodes plus support endpoints; return largest atom too."""
48
+ lo, hi = limits
49
+ order = np.argsort(x, kind="stable")
50
+ x, weights = x[order], weights[order]
51
+ unique, first = np.unique(x, return_index=True)
52
+ # Rescaling before summation avoids overflow without changing probability.
53
+ scaled = weights / weights.max()
54
+ mass = np.add.reduceat(scaled, first)
55
+ mass /= mass.sum()
56
+ cumulative = np.minimum(np.cumsum(mass), 1.0)
57
+ cumulative[-1] = 1.0
58
+ interior = (unique > lo) & (unique < hi)
59
+ knots = np.r_[lo, unique[interior], hi]
60
+ probabilities = np.r_[0.0, cumulative[interior], 1.0]
61
+ return knots, probabilities, float(mass.max())
62
+
63
+
64
+ def build_cdf(x: np.ndarray, weights: np.ndarray, limits: tuple[float, float]) -> PchipInterpolator:
65
+ """Original all-anchor PCHIP path, useful for exact empirical-node comparisons.
66
+
67
+ A sample exactly at lo cannot retain its jump with F(lo)=0; its mass is
68
+ carried into the first interval. Other empirical nodes retain their CDF.
69
+ Production reconstruction uses fit_anchors with the selected settings.
70
+ """
71
+ knots, probabilities, _ = empirical_knots(x, weights, limits)
72
+ return PchipInterpolator(knots, probabilities, extrapolate=False)
73
+
74
+
75
+ def distribution_1d(df: pd.DataFrame, cfg: Mapping[str, Any], kind: str, logger=None) -> pd.DataFrame:
76
+ if kind not in KINDS:
77
+ raise ValueError(f"unknown distribution transform {kind!r}")
78
+ if not isinstance(df, pd.DataFrame) or df.empty:
79
+ raise ValueError(f"{kind} needs a nonempty pandas sample table")
80
+ opts = settings(cfg)
81
+ memtrace_checkpoint(logger, f"pipeline.{kind}.before", df)
82
+ x = _values(df, opts.x["expr"], logger)
83
+ weights = _values(df, opts.weight["expr"], logger) if opts.weight else np.ones(len(df))
84
+ if not np.all(np.isfinite(x)) or not np.all(np.isfinite(weights)):
85
+ raise ValueError(f"{kind} needs finite samples and weights; filter invalid rows first")
86
+ if np.any(weights < 0):
87
+ raise ValueError(f"{kind} needs nonnegative probability weights; signed event weights are not a CDF")
88
+ work = pd.DataFrame({"x": x, "weight": weights})
89
+ if opts.repeat is not None:
90
+ if opts.repeat not in df.columns:
91
+ raise KeyError(f"{kind} missing repeat column {opts.repeat!r}")
92
+ if df[opts.repeat].isna().any():
93
+ raise ValueError(f"{kind} repeat identifiers must not be missing")
94
+ work["repeat"] = df[opts.repeat].to_numpy()
95
+ positive = weights > 0
96
+ if not positive.any():
97
+ raise ValueError(f"{kind} weights sum to zero")
98
+ limits = opts.limits or _infer_limits(x[positive], opts.scale)
99
+ lo, hi = limits
100
+ if np.any((x[positive] < lo) | (x[positive] > hi)):
101
+ raise ValueError(f"{kind} coordinates.x.lim must contain all positive-weight samples; filter first to truncate")
102
+ grid = np.geomspace(lo, hi, opts.grid) if opts.scale == "log" else np.linspace(lo, hi, opts.grid)
103
+ memtrace_object_inventory(logger, f"pipeline.{kind}.samples", {"samples": work})
104
+ curves = ("cdf", "pdf") if kind == "PDF1D" else ("cdf",)
105
+ mean = {key: np.zeros(opts.grid) for key in curves}
106
+ m2 = {key: np.zeros(opts.grid) for key in curves}
107
+ groups = work.groupby("repeat", sort=False, observed=True) if opts.repeat else [(None, work)]
108
+ n = 0
109
+ boundary_mass = False
110
+ diagnostics = []
111
+ for label, block in groups:
112
+ block = block.loc[block.weight > 0]
113
+ if block.empty:
114
+ raise ValueError(f"{kind} repeat {label!r} weights sum to zero")
115
+ bx, bw = block.x.to_numpy(), block.weight.to_numpy()
116
+ boundary_mass |= bool(np.any(bx == lo))
117
+ knots, probabilities, largest_jump = empirical_knots(bx, bw, limits)
118
+ fit = fit_anchors(knots, probabilities, opts.anchors, opts.interpolation)
119
+ cdf = fit.curve
120
+ diagnostics.append({
121
+ "repeat": label.item() if isinstance(label, np.generic) else label,
122
+ "n_anchors": len(fit.indices), "n_candidates": len(knots),
123
+ "max_cdf_error": fit.max_error, "stop_reason": fit.stop_reason,
124
+ "largest_empirical_jump": largest_jump,
125
+ })
126
+ if logger is not None:
127
+ logger.debug(f"{kind} repeat={label!r}: anchors={len(fit.indices)}/{len(knots)}, "
128
+ f"max CDF node error={fit.max_error:.6g}, stopped={fit.stop_reason}")
129
+ if opts.anchors.method == "adaptive" and fit.max_error > opts.anchors.tolerance:
130
+ logger.warning(f"{kind} repeat={label!r}: CDF error {fit.max_error:.6g} exceeds "
131
+ f"tolerance {opts.anchors.tolerance:g}; stopped at {fit.stop_reason}. "
132
+ "Adjust anchors settings if finer CDF reconstruction is needed.")
133
+ if opts.anchors.method == "adaptive" and largest_jump > max(2 * opts.anchors.tolerance, opts.anchors.min_mass):
134
+ logger.warning(f"{kind} repeat={label!r}: empirical probability jump {largest_jump:.6g}; "
135
+ "a continuous density spreads this discrete mass over an interval")
136
+ values = {"cdf": cdf(grid)}
137
+ if kind == "PDF1D":
138
+ pdf = cdf.derivative()(grid)
139
+ tolerance = 128 * np.finfo(float).eps * max(1.0, float(np.max(np.abs(pdf))))
140
+ if np.min(pdf) < -tolerance:
141
+ raise ValueError(f"{kind} produced a negative density beyond floating-point tolerance")
142
+ values["pdf"] = np.maximum(pdf, 0.0)
143
+ n += 1
144
+ for key in curves:
145
+ if not np.all(np.isfinite(values[key])):
146
+ raise ValueError(f"{kind} produced nonfinite {key}; inspect sample spacing/support")
147
+ delta = values[key] - mean[key]
148
+ mean[key] += delta / n
149
+ m2[key] += delta * (values[key] - mean[key])
150
+ out = {opts.name: grid}
151
+ for key in curves:
152
+ out[key] = mean[key]
153
+ out[f"{key}_std"] = np.sqrt(np.maximum(m2[key], 0.0) / (n - 1)) if n > 1 else np.full(opts.grid, np.nan)
154
+ out["n_repeats"] = n
155
+ table = pd.DataFrame(out)
156
+ table.attrs["distribution_1d"] = {
157
+ "algorithm": ALGORITHM_REVISION, "interpolation": opts.interpolation,
158
+ "anchor_method": opts.anchors.method, "support": limits, "repeats": diagnostics,
159
+ }
160
+ if logger is not None:
161
+ if boundary_mass:
162
+ logger.warning(f"{kind}: mass at the lower support boundary is spread over the first CDF interval")
163
+ logger.debug(f"{kind}: {len(df)} samples -> {opts.grid} points, {n} repeats, support={limits}, std ddof=1")
164
+ memtrace_checkpoint(logger, f"pipeline.{kind}.after", table)
165
+ return table
@@ -327,12 +327,16 @@ class DataPreprocessor:
327
327
  )
328
328
 
329
329
  def _transform_input_columns(self, transform: Any) -> List[str]:
330
+ from ..distribution_1d_config import distribution_config, distribution_input_columns, distribution_kind
331
+
330
332
  out: set[str] = set()
331
333
  if not isinstance(transform, list):
332
334
  return []
333
335
  for step in transform:
334
336
  if not isinstance(step, Mapping):
335
337
  continue
338
+ if distribution_kind(step) is not None:
339
+ out.update(distribution_input_columns(distribution_config(step)))
336
340
  if "filter" in step:
337
341
  out.update(self._expr_symbols(step.get("filter")))
338
342
  if "sortby" in step:
@@ -357,12 +361,17 @@ class DataPreprocessor:
357
361
  return sorted(out)
358
362
 
359
363
  def _transform_output_columns(self, transform: Any) -> List[str]:
364
+ from ..distribution_1d_config import distribution_config, distribution_kind, distribution_output_columns
365
+
360
366
  out: set[str] = set()
361
367
  if not isinstance(transform, list):
362
368
  return []
363
369
  for step in transform:
364
370
  if not isinstance(step, Mapping):
365
371
  continue
372
+ kind = distribution_kind(step)
373
+ if kind is not None:
374
+ out.update(distribution_output_columns(kind, distribution_config(step)))
366
375
  if "add_column" in step:
367
376
  add_cfg = step.get("add_column", {})
368
377
  if isinstance(add_cfg, Mapping):
@@ -937,12 +946,18 @@ class DataPreprocessor:
937
946
  return self._stable_hash(payload)
938
947
 
939
948
  def _runtime_profile_tokens(self, transform: Any) -> List[Dict[str, Any]]:
949
+ from ..distribution_1d_config import distribution_kind
950
+ from .distribution_1d_runtime import ALGORITHM_REVISION
951
+
940
952
  tokens: List[Dict[str, Any]] = []
941
953
  if not isinstance(transform, list):
942
954
  return tokens
943
955
  for step in transform:
944
956
  if not isinstance(step, Mapping):
945
957
  continue
958
+ kind = distribution_kind(step)
959
+ if kind is not None:
960
+ tokens.append({"kind": kind, "algo": ALGORITHM_REVISION, "cfg": deepcopy(step)})
946
961
  if "profile" in step:
947
962
  cfg = step.get("profile", {})
948
963
  if isinstance(cfg, Mapping):
@@ -16,6 +16,8 @@ from ..memtrace import memtrace_checkpoint, memtrace_object_inventory
16
16
  from ..utils.expression import eval_dataframe_expression
17
17
  from ..utils.pathing import resolve_project_path
18
18
  from .bin_stat_runtime import bin_stat, bin_stat_config, is_bin_stat_transform
19
+ from ..distribution_1d_config import distribution_config, distribution_kind
20
+ from .distribution_1d_runtime import distribution_1d
19
21
  from .correlation_runtime import correlation, correlation_config, is_correlation_transform
20
22
  from .significance_runtime import (
21
23
  is_significance_transform,
@@ -74,6 +76,8 @@ HEAVY_TRANSFORM_KEYS = frozenset(
74
76
  "make_density_core",
75
77
  "posterior_density",
76
78
  "make_interp_2d",
79
+ "PDF1D",
80
+ "CDF1D",
77
81
  "type",
78
82
  # Not heavy to compute, but cross-block: what a ``to_df`` name resolves
79
83
  # to, and the fact that a producing block hands the layer nothing, only
@@ -599,6 +603,8 @@ def apply_transforms_impl(
599
603
  preprocessor._safe_nrows(df),
600
604
  )
601
605
  )
606
+ elif distribution_kind(trans) is not None:
607
+ df = distribution_1d(df, distribution_config(trans), distribution_kind(trans), preprocessor.logger)
602
608
  elif is_bin_stat_transform(trans):
603
609
  before_rows = preprocessor._safe_nrows(df)
604
610
  df = bin_stat(df, bin_stat_config(trans), preprocessor.logger)
@@ -270,6 +270,11 @@ def _collect_expr_columns(obj: Any, out: Set[str]) -> None:
270
270
  if key == "make_interp_2d":
271
271
  out.update(_interp_2d_cfg_input_columns(v))
272
272
  continue
273
+ if key in {"pdf1d", "cdf1d"}:
274
+ from .distribution_1d_config import distribution_input_columns
275
+
276
+ out.update(distribution_input_columns(v))
277
+ continue
273
278
  if key == "bin_stat":
274
279
  out.update(_bin_stat_cfg_columns(v, "input"))
275
280
  continue
@@ -381,12 +386,17 @@ def _transform_needs_all_columns(transform: Any) -> bool:
381
386
 
382
387
  def _transform_output_columns(transform: Any) -> Set[str]:
383
388
  """Return column names produced as outputs by a transform list."""
389
+ from .distribution_1d_config import distribution_kind, distribution_output_columns
390
+
384
391
  out: Set[str] = set()
385
392
  if not isinstance(transform, list):
386
393
  return out
387
394
  for step in transform:
388
395
  if not isinstance(step, Mapping):
389
396
  continue
397
+ kind = distribution_kind(step)
398
+ if kind is not None:
399
+ out.update(distribution_output_columns(kind, step.get(kind)))
390
400
  if "bin_stat" in step:
391
401
  out.update(_bin_stat_cfg_columns(step.get("bin_stat"), "output"))
392
402
  ccfg = _correlation_transform_config(step)
@@ -154,6 +154,12 @@ class DataSet():
154
154
  "columns": self.columns,
155
155
  "full_load": bool(getattr(self, "full_load", False)),
156
156
  }
157
+ from .distribution_1d_config import distribution_kind
158
+
159
+ if isinstance(self.transform, list) and any(distribution_kind(step) for step in self.transform):
160
+ from .Figure.distribution_1d_runtime import ALGORITHM_REVISION
161
+
162
+ extra["distribution_1d_algorithm"] = ALGORITHM_REVISION
157
163
  if self.type in IN_MEMORY_DATASET_TYPES:
158
164
  # Nothing on disk to fingerprint; identity is the declaration alone.
159
165
  return {"path": None, "size": None, "mtime_ns": None, "md5": None, **extra}
@@ -31,6 +31,8 @@ from .utils.dataframes import polars_to_pandas
31
31
  from .utils.pathing import resolve_project_path
32
32
  from .Figure.preprocessor_runtime import add_column, drop_columns, filter_df, keep_columns, sort_by
33
33
  from .Figure.profile_runtime import profiling
34
+ from .distribution_1d_config import distribution_config, distribution_kind
35
+ from .Figure.distribution_1d_runtime import distribution_1d
34
36
 
35
37
  JP_ROW_IDX = "__jp_row_idx__"
36
38
 
@@ -204,6 +206,8 @@ def apply_dataset_transform(dataset, stage: str = "dataset") -> None:
204
206
  cfg = dict(posterior_density_config(trans))
205
207
  cfg.setdefault("_base_dir", getattr(dataset, "rootpath", None))
206
208
  df = posterior_density(df, cfg, dataset.logger)
209
+ elif distribution_kind(trans) is not None:
210
+ df = distribution_1d(df, distribution_config(trans), distribution_kind(trans), dataset.logger)
207
211
  elif is_interp_2d_transform(trans):
208
212
  df = make_interp_2d(df, interp_2d_config(trans), dataset.logger)
209
213
  elif "sortby" in trans:
@@ -411,7 +415,8 @@ def _apply_dataset_transform_polars(
411
415
  elif "drop_columns" in trans:
412
416
  lf = drop_columns(lf, trans.get("drop_columns"), dataset.logger)
413
417
  elif (
414
- "profile" in trans
418
+ distribution_kind(trans) is not None
419
+ or "profile" in trans
415
420
  or "make_density_core" in trans
416
421
  or "posterior_density" in trans
417
422
  or "make_interp_2d" in trans