quantized-lab 0.9.0__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/PKG-INFO +1 -1
  2. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/pyproject.toml +1 -1
  3. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/__init__.py +1 -1
  4. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure.py +17 -5
  5. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_break.py +10 -4
  6. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_labels.py +46 -14
  7. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_overrides.py +1 -1
  8. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_page.py +4 -0
  9. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_scale.py +4 -1
  10. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_ticks.py +31 -2
  11. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_bumps.py +30 -4
  12. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_scan.py +2 -7
  13. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fitting.py +17 -0
  14. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/curve_style_color.py +13 -8
  15. quantized_lab-0.10.0/src/quantized/io/origin_project/fidelity.py +192 -0
  16. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_layers.py +2 -1
  17. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures.py +13 -3
  18. quantized_lab-0.10.0/src/quantized/io/origin_project/graph_preview.py +156 -0
  19. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_curves.py +87 -22
  20. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/templates.py +5 -3
  21. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/export_figures.py +6 -0
  22. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/export_page.py +2 -0
  23. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/fitting.py +20 -3
  24. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/parsers.py +31 -6
  25. quantized_lab-0.9.0/src/quantized/web/assets/index-BiZzN7J6.css → quantized_lab-0.10.0/src/quantized/web/assets/index-DkU851uO.css +1 -1
  26. quantized_lab-0.10.0/src/quantized/web/assets/index-N-btoovo.js +27 -0
  27. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/web/index.html +2 -2
  28. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/tools/origin_compare/README.md +1 -1
  29. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/tools/visual/README.md +44 -5
  30. quantized_lab-0.9.0/src/quantized/web/assets/index-D8RNGjHO.js +0 -27
  31. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/.gitignore +0 -0
  32. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/LICENSE +0 -0
  33. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/NOTICE +0 -0
  34. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/README.md +0 -0
  35. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/__main__.py +0 -0
  36. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/api.py +0 -0
  37. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/app.py +0 -0
  38. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/__init__.py +0 -0
  39. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/_clipfit.py +0 -0
  40. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/_natural_neighbor.py +0 -0
  41. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/aggregate.py +0 -0
  42. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/backgrounds.py +0 -0
  43. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/baseline.py +0 -0
  44. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/batch_fit.py +0 -0
  45. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/constants.py +0 -0
  46. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/corrections.py +0 -0
  47. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/crystallography.py +0 -0
  48. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/diffusion.py +0 -0
  49. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/electrical.py +0 -0
  50. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/electrochemistry.py +0 -0
  51. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/element_data.json +0 -0
  52. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/element_data.py +0 -0
  53. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/errors.py +0 -0
  54. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_categorical.py +0 -0
  55. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_corner.py +0 -0
  56. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_facets.py +0 -0
  57. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_field.py +0 -0
  58. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_hitmap.py +0 -0
  59. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_map.py +0 -0
  60. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_shapes.py +0 -0
  61. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_statplots.py +0 -0
  62. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_styles.py +0 -0
  63. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/figure_ternary.py +0 -0
  64. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_autoguess.py +0 -0
  65. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_bootstrap.py +0 -0
  66. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_constraints.py +0 -0
  67. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_equation.py +0 -0
  68. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_findxy.py +0 -0
  69. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_models.py +0 -0
  70. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_models_special.py +0 -0
  71. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_odr.py +0 -0
  72. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/fit_stats.py +0 -0
  73. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/formula.py +0 -0
  74. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/global_curve_fit.py +0 -0
  75. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/global_fit.py +0 -0
  76. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/interp2d.py +0 -0
  77. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/linecut.py +0 -0
  78. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/magnetic.py +0 -0
  79. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/magnetometry.py +0 -0
  80. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/map.py +0 -0
  81. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/mcmc.py +0 -0
  82. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/optics.py +0 -0
  83. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/pawley.py +0 -0
  84. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peak_batch.py +0 -0
  85. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peak_fit.py +0 -0
  86. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peak_integrate.py +0 -0
  87. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peak_multifit.py +0 -0
  88. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peak_track.py +0 -0
  89. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peaks.py +0 -0
  90. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/peakshapes.py +0 -0
  91. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/plotting.py +0 -0
  92. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/processing.py +0 -0
  93. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/qspace.py +0 -0
  94. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/reductions.py +0 -0
  95. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/reductions_fft.py +0 -0
  96. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/refl_sld_presets.json +0 -0
  97. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/reflectivity.py +0 -0
  98. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/registry.py +0 -0
  99. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/relaxation.py +0 -0
  100. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/report.py +0 -0
  101. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/report_emit.py +0 -0
  102. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/resample.py +0 -0
  103. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/rsm.py +0 -0
  104. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/rsm_analyze.py +0 -0
  105. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/semiconductor.py +0 -0
  106. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/sld.py +0 -0
  107. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/sld_formula.py +0 -0
  108. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/spectral.py +0 -0
  109. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/statplots.py +0 -0
  110. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats.py +0 -0
  111. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova2.py +0 -0
  112. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova_ext.py +0 -0
  113. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_dist.py +0 -0
  114. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_glm.py +0 -0
  115. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_multivar.py +0 -0
  116. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_roc.py +0 -0
  117. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_survival.py +0 -0
  118. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/stats_tests.py +0 -0
  119. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/substrates.py +0 -0
  120. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/superconductor.py +0 -0
  121. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/surface_fit.py +0 -0
  122. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/surface_models.py +0 -0
  123. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/thermal.py +0 -0
  124. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/thin_film.py +0 -0
  125. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/unit_convert.py +0 -0
  126. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/units.py +0 -0
  127. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/vacuum.py +0 -0
  128. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/calc/xray.py +0 -0
  129. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/cli.py +0 -0
  130. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/datastruct.py +0 -0
  131. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/__init__.py +0 -0
  132. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/_hdf5_layout.py +0 -0
  133. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/_jcamp_asdf.py +0 -0
  134. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/_xrdml_scan.py +0 -0
  135. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/base.py +0 -0
  136. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/bruker_brml.py +0 -0
  137. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/bruker_raw.py +0 -0
  138. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/cif.py +0 -0
  139. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/consolidated.py +0 -0
  140. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/delimited.py +0 -0
  141. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/excel.py +0 -0
  142. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/hdf5.py +0 -0
  143. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/import_filters.py +0 -0
  144. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/import_preview.py +0 -0
  145. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/jcamp.py +0 -0
  146. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/lakeshore.py +0 -0
  147. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/ncnr.py +0 -0
  148. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/netcdf.py +0 -0
  149. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/opus.py +0 -0
  150. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin.py +0 -0
  151. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_com.py +0 -0
  152. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/__init__.py +0 -0
  153. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
  154. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/container.py +0 -0
  155. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
  156. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_text.py +0 -0
  157. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures_opju.py +0 -0
  158. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/notes.py +0 -0
  159. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj.py +0 -0
  160. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
  161. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju.py +0 -0
  162. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
  163. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
  164. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
  165. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
  166. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
  167. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
  168. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_text.py +0 -0
  169. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
  170. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
  171. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/preview.py +0 -0
  172. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree.py +0 -0
  173. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
  174. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows.py +0 -0
  175. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
  176. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer.py +0 -0
  177. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
  178. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/qd.py +0 -0
  179. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/refl1d.py +0 -0
  180. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/registry.py +0 -0
  181. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/report_export.py +0 -0
  182. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/rigaku.py +0 -0
  183. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/sims.py +0 -0
  184. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/spc.py +0 -0
  185. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/xrd_csv.py +0 -0
  186. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/io/xrdml.py +0 -0
  187. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/jobs.py +0 -0
  188. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/plugins/__init__.py +0 -0
  189. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/plugins/contract.py +0 -0
  190. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/plugins/loader.py +0 -0
  191. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/plugins/steps.py +0 -0
  192. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/__init__.py +0 -0
  193. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/_bookcache.py +0 -0
  194. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/_export_common.py +0 -0
  195. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/_payload.py +0 -0
  196. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/_uploadcache.py +0 -0
  197. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/aggregate.py +0 -0
  198. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/baseline.py +0 -0
  199. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/books.py +0 -0
  200. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/calc.py +0 -0
  201. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/corrections.py +0 -0
  202. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/crystallography.py +0 -0
  203. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/diffusion.py +0 -0
  204. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/electrical.py +0 -0
  205. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/electrochemistry.py +0 -0
  206. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/export.py +0 -0
  207. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/export_facets.py +0 -0
  208. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/fitting_bumps.py +0 -0
  209. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/import_template.py +0 -0
  210. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/import_wizard.py +0 -0
  211. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/jobs_api.py +0 -0
  212. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/magnetic.py +0 -0
  213. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/magnetometry.py +0 -0
  214. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/optics.py +0 -0
  215. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/peaks.py +0 -0
  216. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/plot.py +0 -0
  217. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/reductions.py +0 -0
  218. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/reference.py +0 -0
  219. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/reflectivity.py +0 -0
  220. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/report_export.py +0 -0
  221. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/rsm.py +0 -0
  222. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/samples.py +0 -0
  223. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/semiconductor.py +0 -0
  224. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/sld.py +0 -0
  225. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/spectral.py +0 -0
  226. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/statplots.py +0 -0
  227. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/stats.py +0 -0
  228. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/stats_design.py +0 -0
  229. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/substrates.py +0 -0
  230. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/superconductor.py +0 -0
  231. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/thermal.py +0 -0
  232. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/thin_film.py +0 -0
  233. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/vacuum.py +0 -0
  234. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/routes/xray.py +0 -0
  235. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/samples/demo_vsm.csv +0 -0
  236. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/server_launch.py +0 -0
  237. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  238. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  239. {quantized_lab-0.9.0 → quantized_lab-0.10.0}/src/quantized/web/loading.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantized-lab
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines.
5
5
  Project-URL: Homepage, https://github.com/pquarterman17/quantized
6
6
  Project-URL: Documentation, https://github.com/pquarterman17/quantized/wiki
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quantized-lab"
3
- version = "0.9.0"
3
+ version = "0.10.0"
4
4
  description = "Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -7,5 +7,5 @@ Layered, enforced architecture (see CLAUDE.md):
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- __version__ = "0.9.0"
10
+ __version__ = "0.10.0"
11
11
  __all__ = ["__version__"]
@@ -27,7 +27,7 @@ from quantized.calc.figure_labels import safe_mathtext_label # noqa: E402
27
27
  from quantized.calc.figure_overrides import _apply_overrides, _validate_overrides # noqa: E402
28
28
  from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
29
29
  from quantized.calc.figure_styles import FigureStyle, figure_style # noqa: E402
30
- from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
30
+ from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
31
31
 
32
32
  __all__ = ["draw_series_axes", "render_figure", "style_rc"]
33
33
 
@@ -177,6 +177,8 @@ def draw_series_axes(
177
177
  series_styles: Sequence[Mapping[str, Any] | None] | None = None,
178
178
  x_fmt: Mapping[str, Any] | None = None,
179
179
  y_fmt: Mapping[str, Any] | None = None,
180
+ x_step: float | None = None,
181
+ y_step: float | None = None,
180
182
  ) -> list[Any]:
181
183
  """Plot ``series`` into an EXISTING Axes: lines, scales, labels, spines,
182
184
  legend, grid, and the per-figure override sweep (:func:`_apply_overrides`).
@@ -221,8 +223,11 @@ def draw_series_axes(
221
223
  (line,) = ax.plot(xv, yv, label=label, **kw)
222
224
  _apply_fill(ax, xv, yv, series, i, spec, line.get_color())
223
225
  artists.append(line)
224
- apply_axis_scale(ax, "x", resolve_axis_scale(x_scale, x_log))
225
- apply_axis_scale(ax, "y", resolve_axis_scale(y_scale, y_log))
226
+ resolved_x_scale = resolve_axis_scale(x_scale, x_log)
227
+ resolved_y_scale = resolve_axis_scale(y_scale, y_log)
228
+ apply_axis_scale(ax, "x", resolved_x_scale)
229
+ apply_axis_scale(ax, "y", resolved_y_scale)
230
+ apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
226
231
  apply_tick_formats(ax, x_fmt, y_fmt)
227
232
  if title:
228
233
  ax.set_title(title)
@@ -242,9 +247,10 @@ def draw_series_axes(
242
247
  loc=st.legend_location,
243
248
  )
244
249
  if st.grid_alpha > 0:
245
- ax.grid(True, alpha=st.grid_alpha)
250
+ ax.grid(True, which="major", alpha=st.grid_alpha)
251
+ ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
246
252
  else:
247
- ax.grid(False)
253
+ ax.grid(False, which="both")
248
254
  _apply_overrides(fig, ax, st, ov, n_series=len(series))
249
255
  return artists
250
256
 
@@ -270,6 +276,8 @@ def _render_impl(
270
276
  collect_map: bool = False,
271
277
  x_fmt: Mapping[str, Any] | None = None,
272
278
  y_fmt: Mapping[str, Any] | None = None,
279
+ x_step: float | None = None,
280
+ y_step: float | None = None,
273
281
  ) -> bytes | dict[str, Any]:
274
282
  """Render ``series`` (each ``(label, y)``) against ``x`` to image bytes.
275
283
 
@@ -352,6 +360,8 @@ def _render_impl(
352
360
  series_styles=series_styles,
353
361
  x_fmt=x_fmt,
354
362
  y_fmt=y_fmt,
363
+ x_step=x_step,
364
+ y_step=y_step,
355
365
  )
356
366
  fig, ax = plt.subplots(figsize=figsize)
357
367
  try:
@@ -372,6 +382,8 @@ def _render_impl(
372
382
  series_styles=series_styles,
373
383
  x_fmt=x_fmt,
374
384
  y_fmt=y_fmt,
385
+ x_step=x_step,
386
+ y_step=y_step,
375
387
  )
376
388
  if not ov.get("margins"):
377
389
  fig.tight_layout()
@@ -40,7 +40,7 @@ from numpy.typing import ArrayLike, NDArray # noqa: E402
40
40
 
41
41
  from quantized.calc.figure import _plot_kwargs # noqa: E402
42
42
  from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
43
- from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
43
+ from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
44
44
 
45
45
  __all__ = ["render_breaks_impl"]
46
46
 
@@ -65,6 +65,8 @@ def render_breaks_impl(
65
65
  series_styles: Sequence[Mapping[str, Any] | None] | None,
66
66
  x_fmt: Mapping[str, Any] | None = None,
67
67
  y_fmt: Mapping[str, Any] | None = None,
68
+ x_step: float | None = None,
69
+ y_step: float | None = None,
68
70
  ) -> bytes:
69
71
  """Render ``series`` against ``x`` with the x-axis elided over each
70
72
  ``[lo, hi]`` pair in ``breaks`` (already sorted/validated non-overlapping
@@ -100,8 +102,11 @@ def render_breaks_impl(
100
102
  ax.plot(x, np.asarray(y, dtype=float), label=label, **kw)
101
103
  lo, hi = bounds[i]
102
104
  ax.set_xlim(lo, hi)
103
- apply_axis_scale(ax, "x", resolve_axis_scale(x_scale, x_log))
104
- apply_axis_scale(ax, "y", resolve_axis_scale(y_scale, y_log))
105
+ resolved_x_scale = resolve_axis_scale(x_scale, x_log)
106
+ resolved_y_scale = resolve_axis_scale(y_scale, y_log)
107
+ apply_axis_scale(ax, "x", resolved_x_scale)
108
+ apply_axis_scale(ax, "y", resolved_y_scale)
109
+ apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
105
110
  apply_tick_formats(ax, x_fmt, y_fmt)
106
111
  if i == 0:
107
112
  handles, labels_out = ax.get_legend_handles_labels()
@@ -113,7 +118,8 @@ def render_breaks_impl(
113
118
  if not st.box_on:
114
119
  ax.spines["top"].set_visible(False)
115
120
  if st.grid_alpha > 0:
116
- ax.grid(True, alpha=st.grid_alpha)
121
+ ax.grid(True, which="major", alpha=st.grid_alpha)
122
+ ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
117
123
 
118
124
  # Diagonal break glyphs (matplotlib's standard broken-axis recipe):
119
125
  # short strokes angled across each seam, on both the outgoing panel's
@@ -13,17 +13,21 @@ error an export.
13
13
 
14
14
  Screen/export WYSIWYG (bug-hunt fix): the frontend's parser
15
15
  (``frontend/src/lib/richtext.ts``) only accepts a STRICT SUBSET of
16
- mathtext -- Greek letters, ``\\AA \\circ \\times \\cdot \\pm \\prime \\,
17
- \\mathrm \\mathit``, and literal Unicode -- and rejects everything else
18
- (e.g. ``\\frac``, ``\\sqrt``, ``\\sum``) as invalid markup, rendering it
16
+ mathtext -- Greek letters, the symbol/relation/arrow commands in
17
+ ``SUPPORTED_MATHTEXT_COMMANDS`` (``\\AA \\times \\leq \\approx \\infty
18
+ \\rightarrow`` ...), the structural commands ``\\frac \\sqrt`` (MAIN #28),
19
+ ``\\, \\mathrm \\mathit``, and literal Unicode -- and rejects everything else
20
+ (e.g. ``\\hat``, ``\\vec``, ``\\overline``) as invalid markup, rendering it
19
21
  literally on screen. Raw matplotlib accepts a much larger command set, so
20
- without this gate a label like ``$\\frac{1}{2}$`` would render literal text
21
- ("Invalid markup") on screen but an actual fraction in the PDF/SVG export --
22
- a WYSIWYG violation. `_uses_only_supported_commands` enumerates the SAME
23
- command set the frontend parser accepts and rejects (falls back to literal)
24
- any ``$...$`` region using a command outside it, BEFORE the matplotlib trial
25
- parse below ever runs. Keep `SUPPORTED_MATHTEXT_COMMANDS` in sync with
26
- richtext.ts's ``GREEK``/``SYMBOLS`` tables + ``mathrm``/``mathit`` handling.
22
+ without this gate a label like ``$\\vec{v}$`` would render literal text
23
+ ("Invalid markup") on screen but an actual arrow-accented vector in the
24
+ PDF/SVG export -- a WYSIWYG violation. `_uses_only_supported_commands`
25
+ enumerates the SAME command set the frontend parser accepts and rejects
26
+ (falls back to literal) any ``$...$`` region using a command outside it,
27
+ BEFORE the matplotlib trial parse below ever runs. Keep
28
+ `SUPPORTED_MATHTEXT_COMMANDS` in sync with richtext.ts's
29
+ ``GREEK``/``SYMBOLS``/``BIGOPS`` tables + ``mathrm``/``mathit``/``frac``/
30
+ ``sqrt`` handling.
27
31
 
28
32
  Pure layer: string in -> string out. matplotlib is imported lazily (same
29
33
  convention as the figure modules -- the heavy import is paid only on export).
@@ -60,12 +64,40 @@ _GREEK_COMMANDS = frozenset(
60
64
  "Phi", "Psi", "Omega",
61
65
  }
62
66
  )
63
- # Mirrors richtext.ts's `SYMBOLS` table (non-Greek symbol commands).
64
- _SYMBOL_COMMANDS = frozenset({"AA", "circ", "times", "cdot", "pm", "prime"})
67
+ # Mirrors richtext.ts's `SYMBOLS` table (non-Greek symbol commands): base
68
+ # symbols + relations, analysis/geometry glyphs, and arrows. Each is verified
69
+ # to parse in matplotlib mathtext (tools probe), so a label using them renders
70
+ # identically on screen (richtext.ts glyph) and in export (mathtext glyph).
71
+ _SYMBOL_COMMANDS = frozenset(
72
+ {
73
+ "AA", "circ", "times", "cdot", "pm", "mp", "div", "prime",
74
+ # relations
75
+ "leq", "geq", "neq", "approx", "equiv", "sim", "propto", "ll", "gg",
76
+ # analysis / geometry
77
+ "infty", "partial", "nabla", "perp", "parallel", "angle",
78
+ "cdots", "ldots", "dots",
79
+ # arrows
80
+ "rightarrow", "to", "leftarrow", "leftrightarrow", "Rightarrow",
81
+ }
82
+ )
65
83
  # Mirrors richtext.ts's `\mathrm{...}` / `\mathit{...}` style-group handling.
66
84
  _STYLE_COMMANDS = frozenset({"mathrm", "mathit"})
67
-
68
- SUPPORTED_MATHTEXT_COMMANDS = _GREEK_COMMANDS | _SYMBOL_COMMANDS | _STYLE_COMMANDS
85
+ # Mirrors richtext.ts's structural nodes: `\frac{a}{b}`, `\sqrt{x}`,
86
+ # `\sqrt[n]{x}` (MAIN #28). matplotlib renders these natively; the frontend
87
+ # canvas + DOM renderers draw the matching stacked/radical layout.
88
+ _STRUCTURE_COMMANDS = frozenset({"frac", "sqrt"})
89
+ # Mirrors richtext.ts's `BIGOPS` table (large operators). matplotlib stacks
90
+ # \sum/\prod limits over/under and side-scripts \int/\oint inline -- the
91
+ # frontend canvas renderer matches that limits placement.
92
+ _BIGOP_COMMANDS = frozenset({"sum", "prod", "int", "oint"})
93
+
94
+ SUPPORTED_MATHTEXT_COMMANDS = (
95
+ _GREEK_COMMANDS
96
+ | _SYMBOL_COMMANDS
97
+ | _STYLE_COMMANDS
98
+ | _STRUCTURE_COMMANDS
99
+ | _BIGOP_COMMANDS
100
+ )
69
101
 
70
102
 
71
103
  def _uses_only_supported_commands(label: str) -> bool:
@@ -118,7 +118,7 @@ def _apply_overrides(
118
118
  )
119
119
 
120
120
  if "grid" in ov:
121
- ax.grid(bool(ov["grid"]), alpha=st.grid_alpha or 0.3)
121
+ ax.grid(bool(ov["grid"]), which="both", alpha=st.grid_alpha or 0.3)
122
122
 
123
123
  for ann in ov.get("annotations", []):
124
124
  # MAIN #18: a per-annotation `size` (the pointer tool's corner-handle
@@ -115,6 +115,8 @@ class PagePanel:
115
115
  # (a figure page composes several independently-configured plot views).
116
116
  x_fmt: Mapping[str, Any] | None = None
117
117
  y_fmt: Mapping[str, Any] | None = None
118
+ x_step: float | None = None
119
+ y_step: float | None = None
118
120
  series_styles: Sequence[Mapping[str, Any] | None] | None = None
119
121
  overrides: Mapping[str, Any] | None = None
120
122
  label: str | None = None
@@ -256,6 +258,8 @@ def render_figure_page(
256
258
  series_styles=p.series_styles,
257
259
  x_fmt=p.x_fmt,
258
260
  y_fmt=p.y_fmt,
261
+ x_step=p.x_step,
262
+ y_step=p.y_step,
259
263
  )
260
264
  text = p.label if p.label is not None else panel_label(idx, label_format)
261
265
  _place_label(ax, safe_mathtext_label(text), label_pos, st)
@@ -21,7 +21,7 @@ from __future__ import annotations
21
21
  from typing import Any
22
22
 
23
23
  import numpy as np
24
- from matplotlib.ticker import Locator
24
+ from matplotlib.ticker import Locator, LogLocator, NullFormatter
25
25
  from numpy.typing import NDArray
26
26
 
27
27
  __all__ = ["apply_axis_scale", "reciprocal_tick_values", "resolve_axis_scale"]
@@ -115,6 +115,9 @@ def apply_axis_scale(ax: Any, axis: str, scale: str) -> None:
115
115
  set_scale = ax.set_xscale if axis == "x" else ax.set_yscale
116
116
  if scale == "log":
117
117
  set_scale("log")
118
+ target = ax.xaxis if axis == "x" else ax.yaxis
119
+ target.set_minor_locator(LogLocator(base=10.0, subs=tuple(range(2, 10))))
120
+ target.set_minor_formatter(NullFormatter())
118
121
  return
119
122
  if scale == "reciprocal":
120
123
  set_scale("function", functions=(_reciprocal, _reciprocal))
@@ -36,9 +36,9 @@ import math
36
36
  from collections.abc import Mapping
37
37
  from typing import Any
38
38
 
39
- from matplotlib.ticker import Formatter
39
+ from matplotlib.ticker import Formatter, MultipleLocator
40
40
 
41
- __all__ = ["apply_tick_formats", "axis_tick_formatter"]
41
+ __all__ = ["apply_tick_formats", "apply_tick_steps", "axis_tick_formatter"]
42
42
 
43
43
  _MODES = ("fixed", "sci", "eng")
44
44
 
@@ -215,3 +215,32 @@ def apply_tick_formats(
215
215
  yf = axis_tick_formatter(y_fmt)
216
216
  if yf is not None:
217
217
  ax.yaxis.set_major_formatter(yf)
218
+
219
+
220
+ def apply_tick_steps(
221
+ ax: Any,
222
+ x_step: float | None,
223
+ y_step: float | None,
224
+ x_scale: str,
225
+ y_scale: str,
226
+ ) -> None:
227
+ """Apply saved major-tick increments to linear axes only.
228
+
229
+ Origin's decoded ``from/to/step`` triples carry a linear increment. Log
230
+ axes retain their scale-specific locator; reciprocal axes have their own
231
+ locator. Invalid or absent increments leave matplotlib's locator intact.
232
+ ``MultipleLocator`` anchors ticks at integer multiples of the step, the
233
+ same convention as the interactive ``fixedLinearAxisSplits`` helper.
234
+ """
235
+ def apply_one(axis: Any, limits: tuple[float, float], step: float | None) -> None:
236
+ if step is None or not math.isfinite(step) or step <= 0:
237
+ return
238
+ span = abs(float(limits[1]) - float(limits[0]))
239
+ if not math.isfinite(span) or span / step > 1000:
240
+ return
241
+ axis.set_major_locator(MultipleLocator(float(step)))
242
+
243
+ if x_scale == "linear":
244
+ apply_one(ax.xaxis, ax.get_xlim(), x_step)
245
+ if y_scale == "linear":
246
+ apply_one(ax.yaxis, ax.get_ylim(), y_step)
@@ -39,6 +39,12 @@ AbortFn = Callable[[], bool]
39
39
  #: Supported bumps engine ids (bumps.fitters FITTERS ids).
40
40
  BUMPS_ENGINES = ("amoeba", "lm", "de", "dream")
41
41
 
42
+ #: Gelman-Rubin R-hat at/above which DREAM chains are considered NOT converged
43
+ #: (the widely-used 1.1 threshold; < ~1.01 is "well mixed"). Surfaced so the UI
44
+ #: never presents posterior intervals as trustworthy when the chains haven't
45
+ #: mixed (audit P1 #2).
46
+ RHAT_THRESHOLD = 1.1
47
+
42
48
  _INSTALL_HINT = (
43
49
  "bumps is not installed - the optional fit engine needs "
44
50
  "'pip install quantized[bumps]' (or: uv sync --extra bumps)"
@@ -154,9 +160,11 @@ def fit_bumps(
154
160
  saved equation-builder models) or a raw ``f(x, p)`` callable. ``engine``
155
161
  is one of ``BUMPS_ENGINES``: amoeba / lm / de report Hessian-derived
156
162
  uncertainties (``uncertainty_kind='hessian'``); dream samples the
157
- posterior (``uncertainty_kind='posterior'``) and additionally returns
158
- per-parameter medians and central 68% intervals (plus the raw draw
159
- matrix when ``return_samples`` — corner-plot food).
163
+ posterior (``uncertainty_kind='posterior'``) and additionally returns, in
164
+ ``result['posterior']``, per-parameter medians, central 68% intervals,
165
+ ``n_draws``, ``nChains``, and the convergence diagnostics ``rHat`` (per
166
+ parameter), ``rHatMax``, and ``converged`` (Gelman-Rubin, audit P1 #2) —
167
+ plus the raw draw matrix when ``return_samples`` (corner-plot food).
160
168
 
161
169
  ``samples`` / ``burn`` / ``pop`` tune dream only. ``progress_callback``
162
170
  (fraction in [0, 1)) and ``abort_check`` (True -> stop sampling early)
@@ -243,15 +251,33 @@ def fit_bumps(
243
251
  }
244
252
 
245
253
  if engine == "dream":
246
- draw = driver.fitter.state.draw()
254
+ state = driver.fitter.state
255
+ draw = state.draw()
247
256
  pts = np.asarray(draw.points, dtype=float)[:, order]
248
257
  lo68, med, hi68 = (
249
258
  np.asarray(np.percentile(pts, q, axis=0), dtype=float) for q in (16.0, 50.0, 84.0)
250
259
  )
260
+ # Convergence diagnostics (audit P1 #2): the Gelman-Rubin potential
261
+ # scale reduction factor (R-hat) per parameter, so posterior intervals
262
+ # are never presented as trustworthy when the chains haven't mixed.
263
+ # (bumps also warns "Did not converge!" via a separate K-S test; R-hat
264
+ # is the standard, interpretable metric. bumps exposes no clean
265
+ # effective-sample-size, so ESS isn't faked from cross-chain draws.)
266
+ try:
267
+ rhat_all = np.asarray(state.gelman(), dtype=float)
268
+ rhat = [float(rhat_all[i]) for i in order]
269
+ rhat_max = float(np.max(rhat_all)) if rhat_all.size else float("nan")
270
+ except Exception: # noqa: BLE001 — aborted/degenerate state (cf. stderr above)
271
+ rhat = [float("nan")] * n_params
272
+ rhat_max = float("nan")
251
273
  result["posterior"] = {
252
274
  "medians": [float(v) for v in med],
253
275
  "interval68": [[float(a), float(b)] for a, b in zip(lo68, hi68, strict=True)],
254
276
  "n_draws": int(pts.shape[0]),
277
+ "rHat": rhat,
278
+ "rHatMax": rhat_max,
279
+ "converged": bool(math.isfinite(rhat_max) and rhat_max < RHAT_THRESHOLD),
280
+ "nChains": int(getattr(state, "Npop", pop * n_params)),
255
281
  }
256
282
  if return_samples:
257
283
  result["samples"] = pts
@@ -31,7 +31,7 @@ from numpy.typing import ArrayLike, NDArray
31
31
  from quantized.calc.fit_autoguess import auto_guess
32
32
  from quantized.calc.fit_equation import default_guesses, equation_model
33
33
  from quantized.calc.fit_models import FIT_MODELS
34
- from quantized.calc.fitting import curve_fit
34
+ from quantized.calc.fitting import curve_fit, weights_from_dy
35
35
 
36
36
  __all__ = ["aicc_from_aic", "default_candidates", "scan_models"]
37
37
 
@@ -210,12 +210,7 @@ def scan_models(
210
210
 
211
211
  weights: NDArray[np.float64] | None = None
212
212
  if dy is not None:
213
- dyv = np.asarray(dy, dtype=float).ravel()
214
- if dyv.size != n:
215
- raise ValueError("dy must have the same length as x")
216
- if not bool(np.all(np.isfinite(dyv))) or bool(np.any(dyv <= 0)):
217
- raise ValueError("dy entries must be finite and > 0")
218
- weights = np.asarray(1.0 / dyv**2, dtype=float)
213
+ weights = weights_from_dy(dy, n)
219
214
 
220
215
  names = list(models) if models is not None else default_candidates(n)
221
216
  entries: list[dict[str, Any]] = []
@@ -83,6 +83,23 @@ def _numerical_hessian(
83
83
  return hmat
84
84
 
85
85
 
86
+ def weights_from_dy(dy: ArrayLike, n: int) -> NDArray[np.float64]:
87
+ """Per-point 1-sigma errors ``dy`` -> least-squares weights ``1/dy**2``.
88
+
89
+ The single canonical error->weight convention shared by every fit entry
90
+ point (``/fit``, ``/equation/fit``, ``/scan``) so "use the plotted error
91
+ column" means the same thing everywhere. Validates length and that every
92
+ entry is finite and strictly positive (a zero error would demand infinite
93
+ weight).
94
+ """
95
+ dyv = np.asarray(dy, dtype=float).ravel()
96
+ if dyv.size != n:
97
+ raise ValueError("dy must have the same length as x")
98
+ if not bool(np.all(np.isfinite(dyv))) or bool(np.any(dyv <= 0)):
99
+ raise ValueError("dy entries must be finite and > 0")
100
+ return np.asarray(1.0 / dyv**2, dtype=float)
101
+
102
+
86
103
  def curve_fit(
87
104
  x: ArrayLike,
88
105
  y: ArrayLike,
@@ -1,4 +1,4 @@
1
- """Per-curve visual style (color / symbol kind / line-vs-scatter) for Origin
1
+ """Per-curve visual style (color / symbol kind / plot type) for Origin
2
2
  graph curves -- shared by BOTH containers (solved 2026-07-06 against the
3
3
  4-stem ``curve_style.json`` oracle: ``hc2convert`` (.opj), ``Hc2 data``,
4
4
  ``RockingCurve``, ``UnpolPlots`` (.opju)).
@@ -33,11 +33,13 @@ variants -- the 4-byte size difference sits past offset 492):
33
33
 
34
34
  * offset 23 -- **symbol kind** (Origin's symbol-gallery index; 0 = none).
35
35
  Oracle-verified 49/49 (.opj) + 43/43 (.opju).
36
- * offset 76 -- the **line-vs-scatter byte** (``0xc8`` line / ``0xc9``
36
+ * offset 76 -- the **plot-type byte** (``0xc8`` line / ``0xc9``
37
37
  scatter -- the very byte ``opju_codec.curve_plot_style``'s
38
38
  ``8f 01 <style> 83`` tag writes: that tag is this record's sparse chunk
39
- for offset 76). ``0xca`` (Moke) and ``0xe7``/``0xe9`` (PNR corpus) also
40
- occur; unmapped -- omitted, never guessed.
39
+ for offset 76). ``0xca`` is line + symbol: OriginLab's official ``plotxy``
40
+ reference maps plot ids 200/201/202 to line/scatter/line+symbol, matching
41
+ these bytes exactly. ``0xe7``/``0xe9`` (PNR corpus) remain unmapped --
42
+ omitted, never guessed.
41
43
  * offsets 302-305 -- a constant ``0xFFFFFFF7`` "auto" sentinel u32.
42
44
  * offsets 306-309 -- the **symbol color** (ocolor u32 LE), terminator
43
45
  ``0xff`` at 310 (the validity gate: hc2convert's 24 non-oracle anchors
@@ -160,9 +162,11 @@ _SYMBOL_SHAPES = {
160
162
  8: "star",
161
163
  }
162
164
 
163
- # Same byte table opju_codec._STYLE_BYTES validated (fig_pairs oracle);
164
- # 0xca/0xe7/0xe9 also occur in the corpus but are unmapped -- never guessed.
165
- _CONNECT_STYLE = {0xC8: "line", 0xC9: "scatter"}
165
+ # Same byte table opju_codec._STYLE_BYTES validated (fig_pairs oracle).
166
+ # 0xca = Origin plot:=202, officially documented as line + symbol:
167
+ # https://docs.originlab.com/x-function/ref/plotxy/
168
+ # 0xe7/0xe9 remain unmapped -- never guessed.
169
+ _CONNECT_STYLE = {0xC8: "line", 0xC9: "scatter", 0xCA: "line_symbol"}
166
170
 
167
171
  # ── auto/increment colours (2026-07-06, §13.2 #2) ────────────────────────────
168
172
  #
@@ -241,7 +245,8 @@ def style_fields(record: bytes) -> dict[str, str | float]:
241
245
  """Decoded per-curve style keys from one curve-anchor record (raw ``.opj``
242
246
  payload or :func:`opju_style_record` reconstruction): any of ``color``
243
247
  (``"#RRGGBB"``), ``symbol`` (marker shape name), ``style``
244
- (``"line"``/``"scatter"``), ``lineWidth`` / ``symbolSize`` (points, the
248
+ (``"line"``/``"scatter"``/``"line_symbol"``), ``lineWidth`` /
249
+ ``symbolSize`` (points, the
245
250
  1/500-pt u16 fields at offsets 21/25 — 92/92 oracle-exact). Undecodable
246
251
  or implausible fields are simply absent, never defaulted."""
247
252
  out: dict[str, str | float] = {}
@@ -0,0 +1,192 @@
1
+ """Loss-aware fidelity assessment for decoded Origin graph records.
2
+
3
+ The binary decoders intentionally return every record they can identify. This
4
+ module builds the presentation contract separately: actionable figures gain a
5
+ conservative per-figure assessment, while filtered/internal records survive as
6
+ diagnostic summaries in a versioned project manifest. Nothing here mutates or
7
+ reduces the pure decoder output.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Iterable
13
+ from typing import Any, Literal, TypedDict
14
+
15
+ from quantized.io.origin_project.graph_preview import PreviewDiagnostic
16
+
17
+ __all__ = ["assess_origin_figures", "origin_figure_decode_failure"]
18
+
19
+ FidelityStatus = Literal["exact", "best_effort", "reference_only", "unresolved"]
20
+
21
+
22
+ class FigureFidelity(TypedDict):
23
+ status: FidelityStatus
24
+ recovered: list[str]
25
+ omissions: list[str]
26
+
27
+
28
+ class FilteredFigure(TypedDict):
29
+ index: int
30
+ name: str
31
+ layer: int | None
32
+ reason: str
33
+
34
+
35
+ class OriginFidelityManifest(TypedDict):
36
+ version: int
37
+ container: Literal["opj", "opju"]
38
+ status: FidelityStatus
39
+ graph_records_total: int
40
+ graph_records_actionable: int
41
+ graph_records_filtered: int
42
+ omissions: list[str]
43
+ filtered_figures: list[FilteredFigure]
44
+ preview_diagnostics: list[PreviewDiagnostic]
45
+
46
+
47
+ def origin_figure_decode_failure(
48
+ *, container: Literal["opj", "opju"]
49
+ ) -> OriginFidelityManifest:
50
+ """Manifest used when optional graph decode fails but workbooks survive."""
51
+ return {
52
+ "version": 1,
53
+ "container": container,
54
+ "status": "unresolved",
55
+ "graph_records_total": 0,
56
+ "graph_records_actionable": 0,
57
+ "graph_records_filtered": 0,
58
+ "omissions": ["figure_decode_error"],
59
+ "filtered_figures": [],
60
+ "preview_diagnostics": [],
61
+ }
62
+
63
+
64
+ _ALWAYS_OMITTED = [
65
+ "graphic_objects",
66
+ "rich_text_run_formatting",
67
+ "advanced_axis_types",
68
+ ]
69
+
70
+
71
+ def _normalized_sources(source_names: Iterable[str]) -> tuple[str, ...]:
72
+ return tuple(s.strip().lower() for s in source_names if s.strip())
73
+
74
+
75
+ def _source_resolves(fig: dict[str, Any], sources: tuple[str, ...]) -> bool:
76
+ hint = str(fig.get("source_hint") or "").strip().lower()
77
+ return bool(hint) and any(hint == s or hint in s or s in hint for s in sources)
78
+
79
+
80
+ def _filter_reason(fig: dict[str, Any], sources: tuple[str, ...]) -> str | None:
81
+ if fig.get("curves") or _source_resolves(fig, sources):
82
+ return None
83
+ hint = str(fig.get("source_hint") or "").strip()
84
+ return (
85
+ f'source hint "{hint}" did not match an imported workbook'
86
+ if hint
87
+ else "no bound curves or source hint"
88
+ )
89
+
90
+
91
+ def _recovered_groups(fig: dict[str, Any]) -> list[str]:
92
+ recovered = ["axis_ranges", "axis_scales"]
93
+ if fig.get("saved_preview"):
94
+ recovered.append("saved_graph_preview")
95
+ if fig.get("curves"):
96
+ recovered.extend(("curve_bindings", "curve_order"))
97
+ if fig.get("x_title") is not None or fig.get("y_title") is not None:
98
+ recovered.append("axis_titles")
99
+ if fig.get("legend_labels"):
100
+ recovered.append("legend_labels")
101
+ if fig.get("annotation_marks"):
102
+ recovered.append("positioned_annotations")
103
+ if fig.get("region_shades"):
104
+ recovered.append("region_shades")
105
+ if fig.get("frame") and fig.get("page"):
106
+ recovered.append("layer_geometry")
107
+ curves = fig.get("curves")
108
+ if isinstance(curves, list) and curves:
109
+ if any(isinstance(c, dict) and c.get("style") for c in curves):
110
+ recovered.append("curve_style")
111
+ if any(isinstance(c, dict) and c.get("color") for c in curves):
112
+ recovered.append("curve_color")
113
+ if any(isinstance(c, dict) and c.get("symbol") for c in curves):
114
+ recovered.append("marker_shape")
115
+ if any(isinstance(c, dict) and c.get("lineWidth") for c in curves):
116
+ recovered.append("line_width")
117
+ if any(isinstance(c, dict) and c.get("symbolSize") for c in curves):
118
+ recovered.append("symbol_size")
119
+ if any(isinstance(c, dict) and c.get("style") == "line_symbol" for c in curves):
120
+ recovered.append("line_symbol_mode")
121
+ return recovered
122
+
123
+
124
+ def _figure_fidelity(fig: dict[str, Any]) -> FigureFidelity:
125
+ omissions = list(_ALWAYS_OMITTED)
126
+ if not fig.get("saved_preview"):
127
+ omissions.append("saved_graph_preview")
128
+ curves = fig.get("curves")
129
+ if not curves:
130
+ omissions.append("exact_curve_bindings")
131
+ elif isinstance(curves, list):
132
+ if any(not isinstance(c, dict) or not c.get("style") for c in curves):
133
+ omissions.append("some_curve_styles")
134
+ if any(not isinstance(c, dict) or not c.get("color") for c in curves):
135
+ omissions.append("some_curve_colors")
136
+ if not (fig.get("frame") and fig.get("page")):
137
+ omissions.append("layer_geometry")
138
+ return {
139
+ # No current decoder proves every visual property, so "exact" is
140
+ # deliberately reserved for a future preview/oracle comparison gate.
141
+ "status": "best_effort",
142
+ "recovered": _recovered_groups(fig),
143
+ "omissions": omissions,
144
+ }
145
+
146
+
147
+ def assess_origin_figures(
148
+ figures: list[dict[str, Any]],
149
+ *,
150
+ container: Literal["opj", "opju"],
151
+ source_names: Iterable[str] = (),
152
+ preview_diagnostics: list[PreviewDiagnostic] | None = None,
153
+ ) -> tuple[list[dict[str, Any]], OriginFidelityManifest]:
154
+ """Return actionable annotated figures plus a loss-aware project manifest."""
155
+ actionable: list[dict[str, Any]] = []
156
+ filtered: list[FilteredFigure] = []
157
+ project_omissions: set[str] = set()
158
+ sources = _normalized_sources(source_names)
159
+ for index, fig in enumerate(figures):
160
+ reason = _filter_reason(fig, sources)
161
+ if reason is not None:
162
+ layer = fig.get("layer")
163
+ filtered.append(
164
+ {
165
+ "index": index,
166
+ "name": str(fig.get("name") or "SYSTEM"),
167
+ "layer": int(layer) if isinstance(layer, int) else None,
168
+ "reason": reason,
169
+ }
170
+ )
171
+ continue
172
+ fidelity = _figure_fidelity(fig)
173
+ project_omissions.update(fidelity["omissions"])
174
+ actionable.append({**fig, "fidelity": fidelity})
175
+
176
+ if not figures:
177
+ project_omissions.add("no_graph_records")
178
+ elif filtered:
179
+ project_omissions.add("filtered_internal_graph_records")
180
+ status: FidelityStatus = "best_effort" if actionable else "unresolved"
181
+ manifest: OriginFidelityManifest = {
182
+ "version": 1,
183
+ "container": container,
184
+ "status": status,
185
+ "graph_records_total": len(figures),
186
+ "graph_records_actionable": len(actionable),
187
+ "graph_records_filtered": len(filtered),
188
+ "omissions": sorted(project_omissions),
189
+ "filtered_figures": filtered,
190
+ "preview_diagnostics": preview_diagnostics or [],
191
+ }
192
+ return actionable, manifest