jarvisplot 2.1.4__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.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/PKG-INFO +12 -3
  2. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/SOURCES.txt +12 -0
  3. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/PKG-INFO +12 -3
  4. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/README.md +11 -2
  5. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/adapters_rect.py +28 -9
  6. jarvisplot-2.1.6/jarvisplot/Figure/adaptive_cdf_runtime.py +131 -0
  7. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/design_runtime.py +6 -2
  8. jarvisplot-2.1.6/jarvisplot/Figure/distribution_1d_runtime.py +165 -0
  9. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/figure.py +38 -10
  10. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/figure_types.py +11 -1
  11. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_2d_runtime.py +39 -4
  12. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_natural_neighbor.py +274 -8
  13. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/interp_natural_neighbor_exact.py +330 -59
  14. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/layer_runtime.py +113 -93
  15. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_density_runtime.py +1 -0
  16. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/preprocessor.py +45 -3
  17. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/preprocessor_runtime.py +6 -0
  18. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/profile_runtime.py +16 -26
  19. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/capabilities.py +33 -0
  20. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/column_demand.py +25 -1
  21. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/column_probe.py +6 -0
  22. jarvisplot-2.1.6/jarvisplot/data_generation_contracts.py +22 -0
  23. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/data_loader.py +39 -4
  24. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/data_loader_runtime.py +6 -1
  25. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/dataset_types.py +11 -2
  26. jarvisplot-2.1.6/jarvisplot/distribution_1d_config.py +167 -0
  27. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/dryrun_runtime.py +120 -31
  28. jarvisplot-2.1.6/jarvisplot/generated_data.py +131 -0
  29. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/man_methods.py +2 -1
  30. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/man_render_agent.py +28 -3
  31. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/man_transforms.py +24 -21
  32. jarvisplot-2.1.6/jarvisplot/manual_cards/field-interp.yaml +138 -0
  33. jarvisplot-2.1.6/jarvisplot/manual_cards/generated-data.yaml +94 -0
  34. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/manifest.yaml +13 -0
  35. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/method_notes.yaml +37 -5
  36. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-profile-2d.yaml +7 -1
  37. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/yaml-root.yaml +4 -1
  38. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/core/dataset.json +12 -4
  39. jarvisplot-2.1.6/jarvisplot/schema/core/distribution-1d.json +72 -0
  40. jarvisplot-2.1.6/jarvisplot/schema/core/generated-data.json +90 -0
  41. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/core/layer.json +15 -2
  42. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/core/transform.json +8 -0
  43. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/manifest.json +4 -0
  44. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/transform_contracts.py +194 -5
  45. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/validation.py +2 -1
  46. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/cap.py +52 -5
  47. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/man.py +2 -1
  48. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/pyproject.toml +1 -1
  49. jarvisplot-2.1.6/tests/test_adaptive_cdf.py +141 -0
  50. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_capabilities.py +23 -0
  51. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_data_loader_contracts.py +37 -0
  52. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_density_cell_transform.py +36 -0
  53. jarvisplot-2.1.6/tests/test_distribution_1d_transform.py +234 -0
  54. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_figure_contracts.py +43 -0
  55. jarvisplot-2.1.6/tests/test_generated_data.py +276 -0
  56. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_interp_2d_transform.py +99 -9
  57. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_interp_natural_neighbor.py +29 -0
  58. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_jpcontour_methods.py +12 -3
  59. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_man_cli.py +18 -0
  60. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_profile_runtime.py +51 -1
  61. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_schema_catalog.py +3 -2
  62. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  63. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  64. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/requires.txt +0 -0
  65. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/JarvisPLOT.egg-info/top_level.txt +0 -0
  66. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/__init__.py +0 -0
  67. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/adapters_ternary.py +0 -0
  68. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
  69. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  70. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  71. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/config_runtime.py +0 -0
  72. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_layout.py +0 -0
  73. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
  74. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/corr_order.py +0 -0
  75. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/correlation_runtime.py +0 -0
  76. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/corrplot_runtime.py +0 -0
  77. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/data_pipelines.py +0 -0
  78. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/debug_config.py +0 -0
  79. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  80. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  81. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/helper.py +0 -0
  82. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/layout_runtime.py +0 -0
  83. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/method_registry.py +0 -0
  84. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_hpd.py +0 -0
  85. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/posterior_mesh.py +0 -0
  86. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/significance_runtime.py +0 -0
  87. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/Figure/style_runtime.py +0 -0
  88. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/__init__.py +0 -0
  89. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/agent_digest.py +0 -0
  90. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/agent_io.py +0 -0
  91. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cache_store.py +0 -0
  92. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  93. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  94. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect.json +0 -0
  95. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  96. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  97. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +0 -0
  98. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/rectratio.json +0 -0
  99. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  100. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  101. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  102. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  103. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  104. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  105. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/args.json +0 -0
  106. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/colors/colormaps.json +0 -0
  107. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/corrplot/diamond.json +0 -0
  108. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/corrplot/matrix.json +0 -0
  109. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/default/output.json +0 -0
  110. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/flowchart/default.json +0 -0
  111. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  112. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  113. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  114. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  115. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  116. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  117. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  118. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  119. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  120. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  121. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  122. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/gambit.png +0 -0
  123. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/icons/gambit_small.png +0 -0
  124. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cards/style_preference.json +0 -0
  125. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cli.py +0 -0
  126. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/cli_help.py +0 -0
  127. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/client.py +0 -0
  128. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/config.py +0 -0
  129. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/config_address.py +0 -0
  130. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/core.py +0 -0
  131. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/core_assets.py +0 -0
  132. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/core_runtime.py +0 -0
  133. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/data_access.py +0 -0
  134. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/data_loader_hdf5.py +0 -0
  135. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/data_loader_summary.py +0 -0
  136. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/diagnostic_guidance.py +0 -0
  137. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/diagnostics.py +0 -0
  138. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/expr_names.py +0 -0
  139. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/fix_apply.py +0 -0
  140. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/flowchart.py +0 -0
  141. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/inner_func.py +0 -0
  142. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/man_catalog.py +0 -0
  143. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/man_render_human.py +0 -0
  144. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  145. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  146. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  147. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
  148. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  149. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  150. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/layer-method.yaml +0 -0
  151. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/style-axes.yaml +0 -0
  152. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
  153. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  154. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/validate.yaml +0 -0
  155. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/manual_cards/workflow.yaml +0 -0
  156. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/memtrace.py +0 -0
  157. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/method_contracts.py +0 -0
  158. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/render_health.py +0 -0
  159. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/core/common.json +0 -0
  160. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/core/figure.json +0 -0
  161. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/bar.json +0 -0
  162. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/barh.json +0 -0
  163. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/contour.json +0 -0
  164. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/contourf.json +0 -0
  165. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/corrplot.json +0 -0
  166. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  167. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/errorbar.json +0 -0
  168. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill.json +0 -0
  169. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill_between.json +0 -0
  170. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  171. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/hist.json +0 -0
  172. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/hist2d.json +0 -0
  173. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/imshow.json +0 -0
  174. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpcontour.json +0 -0
  175. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  176. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/jpfield.json +0 -0
  177. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/pcolor.json +0 -0
  178. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  179. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/plot.json +0 -0
  180. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/quiver.json +0 -0
  181. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/scatter.json +0 -0
  182. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/stairs.json +0 -0
  183. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/step.json +0 -0
  184. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tricontour.json +0 -0
  185. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tricontourf.json +0 -0
  186. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tripcolor.json +0 -0
  187. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  188. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/triplot.json +0 -0
  189. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/voronoi.json +0 -0
  190. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/methods/voronoif.json +0 -0
  191. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema/plot-config.json +0 -0
  192. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema_catalog.py +0 -0
  193. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/schema_diagnostics.py +0 -0
  194. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/templates_catalog.py +0 -0
  195. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/__init__.py +0 -0
  196. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/cmaps.py +0 -0
  197. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/dataframes.py +0 -0
  198. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/expression.py +0 -0
  199. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/interpolator.py +0 -0
  200. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/utils/pathing.py +0 -0
  201. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/__init__.py +0 -0
  202. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/config_cmd.py +0 -0
  203. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/data.py +0 -0
  204. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/doctor.py +0 -0
  205. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/dryrun.py +0 -0
  206. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/explain.py +0 -0
  207. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/suggest.py +0 -0
  208. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/template.py +0 -0
  209. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/verbs/validate.py +0 -0
  210. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/jarvisplot/yaml_io.py +0 -0
  211. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/setup.cfg +0 -0
  212. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_agent_digest.py +0 -0
  213. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_agent_envelope.py +0 -0
  214. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_axis_title.py +0 -0
  215. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_card_debug_shape.py +0 -0
  216. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_cli_help.py +0 -0
  217. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_cli_verbs.py +0 -0
  218. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_cmaps.py +0 -0
  219. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_column_existence.py +0 -0
  220. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_config_addressing.py +0 -0
  221. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_config_edit.py +0 -0
  222. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_config_expand.py +0 -0
  223. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_correlation_runtime.py +0 -0
  224. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_corrplot.py +0 -0
  225. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_data_access.py +0 -0
  226. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_data_describe.py +0 -0
  227. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_data_head_eval.py +0 -0
  228. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_data_pipelines.py +0 -0
  229. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_debug_config.py +0 -0
  230. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_debug_switches.py +0 -0
  231. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_debug_yaml_override.py +0 -0
  232. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_design_runtime.py +0 -0
  233. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_diagnostic_guidance.py +0 -0
  234. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_docs_status_labels.py +0 -0
  235. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_doctor_partial.py +0 -0
  236. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_explain.py +0 -0
  237. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_figure_type_expander.py +0 -0
  238. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_fix_application.py +0 -0
  239. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_flowchart_renderer.py +0 -0
  240. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_imshow_adapter.py +0 -0
  241. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_interpolator_contracts.py +0 -0
  242. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_method_contracts.py +0 -0
  243. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_parse_data_metadata.py +0 -0
  244. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_pathing_contracts.py +0 -0
  245. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_posterior_hpd.py +0 -0
  246. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_render_health.py +0 -0
  247. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_row_ledger.py +0 -0
  248. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_suggest.py +0 -0
  249. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_suggest_axes.py +0 -0
  250. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_template_catalog.py +0 -0
  251. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_template_json.py +0 -0
  252. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_ternary_debug.py +0 -0
  253. {jarvisplot-2.1.4 → jarvisplot-2.1.6}/tests/test_transform_contracts.py +0 -0
  254. {jarvisplot-2.1.4 → 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.4
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;
@@ -22,6 +22,7 @@ jarvisplot/core.py
22
22
  jarvisplot/core_assets.py
23
23
  jarvisplot/core_runtime.py
24
24
  jarvisplot/data_access.py
25
+ jarvisplot/data_generation_contracts.py
25
26
  jarvisplot/data_loader.py
26
27
  jarvisplot/data_loader_hdf5.py
27
28
  jarvisplot/data_loader_runtime.py
@@ -29,10 +30,12 @@ jarvisplot/data_loader_summary.py
29
30
  jarvisplot/dataset_types.py
30
31
  jarvisplot/diagnostic_guidance.py
31
32
  jarvisplot/diagnostics.py
33
+ jarvisplot/distribution_1d_config.py
32
34
  jarvisplot/dryrun_runtime.py
33
35
  jarvisplot/expr_names.py
34
36
  jarvisplot/fix_apply.py
35
37
  jarvisplot/flowchart.py
38
+ jarvisplot/generated_data.py
36
39
  jarvisplot/inner_func.py
37
40
  jarvisplot/man_catalog.py
38
41
  jarvisplot/man_methods.py
@@ -51,6 +54,7 @@ jarvisplot/yaml_io.py
51
54
  jarvisplot/Figure/__init__.py
52
55
  jarvisplot/Figure/adapters_rect.py
53
56
  jarvisplot/Figure/adapters_ternary.py
57
+ jarvisplot/Figure/adaptive_cdf_runtime.py
54
58
  jarvisplot/Figure/bin_stat_runtime.py
55
59
  jarvisplot/Figure/colorbar_runtime.py
56
60
  jarvisplot/Figure/config_runtime.py
@@ -63,6 +67,7 @@ jarvisplot/Figure/data_pipelines.py
63
67
  jarvisplot/Figure/debug_config.py
64
68
  jarvisplot/Figure/density_cell_runtime.py
65
69
  jarvisplot/Figure/design_runtime.py
70
+ jarvisplot/Figure/distribution_1d_runtime.py
66
71
  jarvisplot/Figure/dynesty_runtime.py
67
72
  jarvisplot/Figure/figure.py
68
73
  jarvisplot/Figure/figure_types.py
@@ -121,6 +126,8 @@ jarvisplot/manual_cards/data-columns.yaml
121
126
  jarvisplot/manual_cards/debug-overlay.yaml
122
127
  jarvisplot/manual_cards/diagnostics.yaml
123
128
  jarvisplot/manual_cards/dryrun-viz.yaml
129
+ jarvisplot/manual_cards/field-interp.yaml
130
+ jarvisplot/manual_cards/generated-data.yaml
124
131
  jarvisplot/manual_cards/layer-method.yaml
125
132
  jarvisplot/manual_cards/manifest.yaml
126
133
  jarvisplot/manual_cards/method_notes.yaml
@@ -135,7 +142,9 @@ jarvisplot/schema/manifest.json
135
142
  jarvisplot/schema/plot-config.json
136
143
  jarvisplot/schema/core/common.json
137
144
  jarvisplot/schema/core/dataset.json
145
+ jarvisplot/schema/core/distribution-1d.json
138
146
  jarvisplot/schema/core/figure.json
147
+ jarvisplot/schema/core/generated-data.json
139
148
  jarvisplot/schema/core/layer.json
140
149
  jarvisplot/schema/core/transform.json
141
150
  jarvisplot/schema/methods/bar.json
@@ -185,6 +194,7 @@ jarvisplot/verbs/man.py
185
194
  jarvisplot/verbs/suggest.py
186
195
  jarvisplot/verbs/template.py
187
196
  jarvisplot/verbs/validate.py
197
+ tests/test_adaptive_cdf.py
188
198
  tests/test_agent_digest.py
189
199
  tests/test_agent_envelope.py
190
200
  tests/test_axis_title.py
@@ -210,6 +220,7 @@ tests/test_debug_yaml_override.py
210
220
  tests/test_density_cell_transform.py
211
221
  tests/test_design_runtime.py
212
222
  tests/test_diagnostic_guidance.py
223
+ tests/test_distribution_1d_transform.py
213
224
  tests/test_docs_status_labels.py
214
225
  tests/test_doctor_partial.py
215
226
  tests/test_explain.py
@@ -217,6 +228,7 @@ tests/test_figure_contracts.py
217
228
  tests/test_figure_type_expander.py
218
229
  tests/test_fix_application.py
219
230
  tests/test_flowchart_renderer.py
231
+ tests/test_generated_data.py
220
232
  tests/test_imshow_adapter.py
221
233
  tests/test_interp_2d_transform.py
222
234
  tests/test_interp_natural_neighbor.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.4
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;
@@ -12,7 +12,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
14
  from .profile_runtime import grid_from_metadata, regular_grid_mesh
15
- from .interp_natural_neighbor import resolve_backend
15
+ from .interp_natural_neighbor import (
16
+ drawing_backend_options,
17
+ log_backend_diagnostics,
18
+ resolve_backend,
19
+ )
16
20
  from .posterior_hpd import prepare_hpd_contour_style
17
21
 
18
22
  DEFAULT_JP_SAMPLE_GRID = 500
@@ -242,8 +246,9 @@ class StdAxesAdapter:
242
246
  ny: Optional[int] = None,
243
247
  xlim=None,
244
248
  ylim=None,
245
- nan_policy: str = "strict",
249
+ nan_policy: str = "ignore",
246
250
  backend_options: Optional[dict[str, Any]] = None,
251
+ logger=None,
247
252
  ):
248
253
  x = np.asarray(x, dtype=float).reshape(-1)
249
254
  y = np.asarray(y, dtype=float).reshape(-1)
@@ -273,8 +278,9 @@ class StdAxesAdapter:
273
278
  X,
274
279
  Y,
275
280
  nan_policy=nan_policy,
276
- backend_options=backend_options,
281
+ backend_options=drawing_backend_options(backend_options),
277
282
  )
283
+ log_backend_diagnostics(logger, getattr(backend, "last_diagnostics", None))
278
284
  return X, Y, np.asarray(Z, dtype=float)
279
285
 
280
286
  def _build_jpcontour_grid(
@@ -322,9 +328,10 @@ class StdAxesAdapter:
322
328
  ny: Optional[int] = None,
323
329
  xlim=None,
324
330
  ylim=None,
325
- nan_policy: str = "strict",
331
+ nan_policy: str = "ignore",
326
332
  diagnostics: bool = False,
327
333
  backend_options: Optional[dict[str, Any]] = None,
334
+ logger=None,
328
335
  ):
329
336
  """Interpolate scattered data on an axes-fraction grid for contour rendering.
330
337
 
@@ -361,8 +368,9 @@ class StdAxesAdapter:
361
368
  Y,
362
369
  nan_policy=nan_policy,
363
370
  diagnostics=diagnostics,
364
- backend_options=backend_options,
371
+ backend_options=drawing_backend_options(backend_options),
365
372
  )
373
+ log_backend_diagnostics(logger, getattr(backend, "last_diagnostics", None))
366
374
  return X, Y, np.asarray(Z, dtype=float)
367
375
 
368
376
  # —— Common method forwarding (add as needed) ——
@@ -518,12 +526,18 @@ class StdAxesAdapter:
518
526
  ny: Optional[int] = None,
519
527
  xlim=None,
520
528
  ylim=None,
521
- nan_policy: str = "strict",
529
+ nan_policy: str = "ignore",
522
530
  diagnostics: bool = False,
523
531
  backend_options: Optional[dict[str, Any]] = None,
524
532
  **kwargs,
525
533
  ):
526
- """Scattered-data contour plot rendered in axes-fraction coordinates."""
534
+ """Scattered-data contour plot rendered in axes-fraction coordinates.
535
+
536
+ ``nan_policy`` defaults to ``ignore`` here: a sample whose z is NaN --
537
+ an empty profile-likelihood cell, say -- is not a measurement, and
538
+ letting it propagate would blank the cells around it as well. Pass
539
+ ``nan_policy="strict"`` to keep those neighbourhoods masked instead.
540
+ """
527
541
  kw = self._merge("contour", kwargs)
528
542
  kw.pop("transform", None)
529
543
  logger = kw.pop("_logger", None)
@@ -540,6 +554,7 @@ class StdAxesAdapter:
540
554
  nan_policy=nan_policy,
541
555
  diagnostics=diagnostics,
542
556
  backend_options=backend_options,
557
+ logger=logger,
543
558
  )
544
559
  kw = prepare_hpd_contour_style(Z, X, Y, kw, logger=logger)
545
560
  label_map = kw.pop("_hpd_label_map", None)
@@ -570,7 +585,7 @@ class StdAxesAdapter:
570
585
  ny: Optional[int] = None,
571
586
  xlim=None,
572
587
  ylim=None,
573
- nan_policy: str = "strict",
588
+ nan_policy: str = "ignore",
574
589
  diagnostics: bool = False,
575
590
  backend_options: Optional[dict[str, Any]] = None,
576
591
  **kwargs,
@@ -578,6 +593,7 @@ class StdAxesAdapter:
578
593
  """Filled scattered-data contour plot rendered in axes-fraction coordinates."""
579
594
  kw = self._merge("contourf", kwargs)
580
595
  kw.pop("transform", None)
596
+ logger = kw.pop("_logger", None)
581
597
  X, Y, Z = self._interpolate_jpcontour_grid(
582
598
  x,
583
599
  y,
@@ -591,6 +607,7 @@ class StdAxesAdapter:
591
607
  nan_policy=nan_policy,
592
608
  diagnostics=diagnostics,
593
609
  backend_options=backend_options,
610
+ logger=logger,
594
611
  )
595
612
  artists = self.ax.contourf(
596
613
  X,
@@ -614,7 +631,7 @@ class StdAxesAdapter:
614
631
  ny: Optional[int] = None,
615
632
  xlim=None,
616
633
  ylim=None,
617
- nan_policy: str = "strict",
634
+ nan_policy: str = "ignore",
618
635
  backend_options: Optional[dict[str, Any]] = None,
619
636
  shading: str = "auto",
620
637
  **kwargs,
@@ -633,6 +650,7 @@ class StdAxesAdapter:
633
650
  kw.pop(k, None)
634
651
  kw.pop("shading", None)
635
652
  kw.pop("transform", None)
653
+ logger = kw.pop("_logger", None)
636
654
  X, Y, Z = self._interpolate_jpfield_grid(
637
655
  x,
638
656
  y,
@@ -645,6 +663,7 @@ class StdAxesAdapter:
645
663
  ylim=ylim,
646
664
  nan_policy=nan_policy,
647
665
  backend_options=backend_options,
666
+ logger=logger,
648
667
  )
649
668
  X = np.asarray(X, dtype=float)
650
669
  Y = np.asarray(Y, dtype=float)
@@ -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)
@@ -129,8 +129,12 @@ def _preview_colorbar(fig, name, cfg):
129
129
 
130
130
  from .colorbar_runtime import axc_color_config, axc_is_horizontal
131
131
 
132
- cmap = axc_color_config(fig.frame, name).get("cmap") or cfg["fallback_cmap"]
133
- is_h = axc_is_horizontal(fig.frame, name)
132
+ # Lightweight wrappers used by design tooling may expose only ``fig`` and
133
+ # ``axes``. An absent frame means there is no colorbar override, not that
134
+ # the rest of the debug overlay should be abandoned halfway through.
135
+ frame = getattr(fig, "frame", {})
136
+ cmap = axc_color_config(frame, name).get("cmap") or cfg["fallback_cmap"]
137
+ is_h = axc_is_horizontal(frame, name)
134
138
  ramp = np.linspace(0.0, 1.0, int(cfg["samples"])).reshape(-1, 1)
135
139
  if is_h:
136
140
  ramp = ramp.reshape(1, -1)
@@ -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