quantized-lab 0.8.0__py3-none-any.whl

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 (233) hide show
  1. quantized/__init__.py +11 -0
  2. quantized/__main__.py +14 -0
  3. quantized/api.py +220 -0
  4. quantized/app.py +206 -0
  5. quantized/calc/__init__.py +8 -0
  6. quantized/calc/_clipfit.py +76 -0
  7. quantized/calc/_natural_neighbor.py +219 -0
  8. quantized/calc/aggregate.py +159 -0
  9. quantized/calc/backgrounds.py +353 -0
  10. quantized/calc/baseline.py +349 -0
  11. quantized/calc/batch_fit.py +148 -0
  12. quantized/calc/constants.py +27 -0
  13. quantized/calc/corrections.py +192 -0
  14. quantized/calc/crystallography.py +400 -0
  15. quantized/calc/diffusion.py +120 -0
  16. quantized/calc/electrical.py +248 -0
  17. quantized/calc/electrochemistry.py +176 -0
  18. quantized/calc/element_data.json +1 -0
  19. quantized/calc/element_data.py +59 -0
  20. quantized/calc/errors.py +246 -0
  21. quantized/calc/figure.py +417 -0
  22. quantized/calc/figure_break.py +152 -0
  23. quantized/calc/figure_categorical.py +156 -0
  24. quantized/calc/figure_corner.py +229 -0
  25. quantized/calc/figure_facets.py +127 -0
  26. quantized/calc/figure_field.py +137 -0
  27. quantized/calc/figure_hitmap.py +116 -0
  28. quantized/calc/figure_labels.py +62 -0
  29. quantized/calc/figure_map.py +287 -0
  30. quantized/calc/figure_overrides.py +159 -0
  31. quantized/calc/figure_page.py +266 -0
  32. quantized/calc/figure_scale.py +125 -0
  33. quantized/calc/figure_statplots.py +167 -0
  34. quantized/calc/figure_styles.py +131 -0
  35. quantized/calc/figure_ternary.py +239 -0
  36. quantized/calc/figure_ticks.py +217 -0
  37. quantized/calc/fit_autoguess.py +156 -0
  38. quantized/calc/fit_bootstrap.py +163 -0
  39. quantized/calc/fit_bumps.py +258 -0
  40. quantized/calc/fit_constraints.py +114 -0
  41. quantized/calc/fit_equation.py +264 -0
  42. quantized/calc/fit_findxy.py +80 -0
  43. quantized/calc/fit_models.py +195 -0
  44. quantized/calc/fit_models_special.py +189 -0
  45. quantized/calc/fit_odr.py +99 -0
  46. quantized/calc/fit_scan.py +243 -0
  47. quantized/calc/fit_stats.py +215 -0
  48. quantized/calc/fitting.py +199 -0
  49. quantized/calc/formula.py +90 -0
  50. quantized/calc/global_curve_fit.py +305 -0
  51. quantized/calc/global_fit.py +181 -0
  52. quantized/calc/interp2d.py +260 -0
  53. quantized/calc/linecut.py +269 -0
  54. quantized/calc/magnetic.py +414 -0
  55. quantized/calc/magnetometry.py +464 -0
  56. quantized/calc/map.py +228 -0
  57. quantized/calc/mcmc.py +177 -0
  58. quantized/calc/optics.py +228 -0
  59. quantized/calc/pawley.py +251 -0
  60. quantized/calc/peak_batch.py +128 -0
  61. quantized/calc/peak_fit.py +259 -0
  62. quantized/calc/peak_integrate.py +104 -0
  63. quantized/calc/peak_multifit.py +260 -0
  64. quantized/calc/peak_track.py +134 -0
  65. quantized/calc/peaks.py +298 -0
  66. quantized/calc/peakshapes.py +85 -0
  67. quantized/calc/plotting.py +147 -0
  68. quantized/calc/processing.py +232 -0
  69. quantized/calc/qspace.py +48 -0
  70. quantized/calc/reductions.py +155 -0
  71. quantized/calc/reductions_fft.py +383 -0
  72. quantized/calc/refl_sld_presets.json +1 -0
  73. quantized/calc/reflectivity.py +80 -0
  74. quantized/calc/registry.py +303 -0
  75. quantized/calc/relaxation.py +119 -0
  76. quantized/calc/report.py +253 -0
  77. quantized/calc/report_emit.py +227 -0
  78. quantized/calc/resample.py +142 -0
  79. quantized/calc/rsm.py +91 -0
  80. quantized/calc/rsm_analyze.py +245 -0
  81. quantized/calc/semiconductor.py +488 -0
  82. quantized/calc/sld.py +131 -0
  83. quantized/calc/sld_formula.py +138 -0
  84. quantized/calc/spectral.py +357 -0
  85. quantized/calc/statplots.py +214 -0
  86. quantized/calc/stats.py +399 -0
  87. quantized/calc/stats_anova2.py +202 -0
  88. quantized/calc/stats_anova_ext.py +338 -0
  89. quantized/calc/stats_dist.py +196 -0
  90. quantized/calc/stats_glm.py +245 -0
  91. quantized/calc/stats_multivar.py +289 -0
  92. quantized/calc/stats_roc.py +157 -0
  93. quantized/calc/stats_survival.py +261 -0
  94. quantized/calc/stats_tests.py +380 -0
  95. quantized/calc/substrates.py +181 -0
  96. quantized/calc/superconductor.py +359 -0
  97. quantized/calc/surface_fit.py +290 -0
  98. quantized/calc/surface_models.py +156 -0
  99. quantized/calc/thermal.py +119 -0
  100. quantized/calc/thin_film.py +425 -0
  101. quantized/calc/unit_convert.py +259 -0
  102. quantized/calc/units.py +80 -0
  103. quantized/calc/vacuum.py +290 -0
  104. quantized/calc/xray.py +169 -0
  105. quantized/cli.py +214 -0
  106. quantized/datastruct.py +153 -0
  107. quantized/io/__init__.py +11 -0
  108. quantized/io/_hdf5_layout.py +308 -0
  109. quantized/io/_jcamp_asdf.py +135 -0
  110. quantized/io/_xrdml_scan.py +291 -0
  111. quantized/io/base.py +82 -0
  112. quantized/io/bruker_brml.py +177 -0
  113. quantized/io/bruker_raw.py +158 -0
  114. quantized/io/cif.py +266 -0
  115. quantized/io/consolidated.py +122 -0
  116. quantized/io/delimited.py +222 -0
  117. quantized/io/excel.py +135 -0
  118. quantized/io/hdf5.py +192 -0
  119. quantized/io/import_filters.py +178 -0
  120. quantized/io/import_preview.py +262 -0
  121. quantized/io/jcamp.py +179 -0
  122. quantized/io/lakeshore.py +163 -0
  123. quantized/io/ncnr.py +278 -0
  124. quantized/io/netcdf.py +195 -0
  125. quantized/io/opus.py +231 -0
  126. quantized/io/origin.py +346 -0
  127. quantized/io/origin_com.py +194 -0
  128. quantized/io/origin_project/__init__.py +221 -0
  129. quantized/io/origin_project/annotation_marks.py +288 -0
  130. quantized/io/origin_project/container.py +262 -0
  131. quantized/io/origin_project/curve_style_color.py +359 -0
  132. quantized/io/origin_project/figure_geometry.py +108 -0
  133. quantized/io/origin_project/figure_layers.py +333 -0
  134. quantized/io/origin_project/figure_text.py +258 -0
  135. quantized/io/origin_project/figures.py +210 -0
  136. quantized/io/origin_project/figures_opju.py +440 -0
  137. quantized/io/origin_project/notes.py +302 -0
  138. quantized/io/origin_project/opj.py +459 -0
  139. quantized/io/origin_project/opj_curves.py +297 -0
  140. quantized/io/origin_project/opj_shapes.py +148 -0
  141. quantized/io/origin_project/opju.py +146 -0
  142. quantized/io/origin_project/opju_axis_real_form.py +418 -0
  143. quantized/io/origin_project/opju_axis_specimen_form.py +167 -0
  144. quantized/io/origin_project/opju_codec.py +370 -0
  145. quantized/io/origin_project/opju_curves.py +497 -0
  146. quantized/io/origin_project/opju_curves_allcols.py +258 -0
  147. quantized/io/origin_project/opju_figure_curves.py +302 -0
  148. quantized/io/origin_project/opju_figure_text.py +245 -0
  149. quantized/io/origin_project/opju_reports.py +129 -0
  150. quantized/io/origin_project/origin_richtext.py +145 -0
  151. quantized/io/origin_project/preview.py +132 -0
  152. quantized/io/origin_project/templates.py +314 -0
  153. quantized/io/origin_project/tree.py +379 -0
  154. quantized/io/origin_project/tree_opju.py +228 -0
  155. quantized/io/origin_project/windows.py +238 -0
  156. quantized/io/origin_project/windows_opju.py +393 -0
  157. quantized/io/origin_project/writer.py +156 -0
  158. quantized/io/origin_project/writer_blocks.py +282 -0
  159. quantized/io/qd.py +380 -0
  160. quantized/io/refl1d.py +132 -0
  161. quantized/io/registry.py +210 -0
  162. quantized/io/report_export.py +347 -0
  163. quantized/io/rigaku.py +100 -0
  164. quantized/io/sims.py +398 -0
  165. quantized/io/spc.py +311 -0
  166. quantized/io/xrd_csv.py +308 -0
  167. quantized/io/xrdml.py +394 -0
  168. quantized/jobs.py +173 -0
  169. quantized/plugins/__init__.py +50 -0
  170. quantized/plugins/contract.py +111 -0
  171. quantized/plugins/loader.py +394 -0
  172. quantized/plugins/steps.py +90 -0
  173. quantized/routes/__init__.py +7 -0
  174. quantized/routes/_bookcache.py +62 -0
  175. quantized/routes/_export_common.py +27 -0
  176. quantized/routes/_payload.py +58 -0
  177. quantized/routes/_uploadcache.py +59 -0
  178. quantized/routes/aggregate.py +46 -0
  179. quantized/routes/baseline.py +210 -0
  180. quantized/routes/books.py +117 -0
  181. quantized/routes/calc.py +56 -0
  182. quantized/routes/corrections.py +78 -0
  183. quantized/routes/crystallography.py +80 -0
  184. quantized/routes/diffusion.py +58 -0
  185. quantized/routes/electrical.py +101 -0
  186. quantized/routes/electrochemistry.py +83 -0
  187. quantized/routes/export.py +280 -0
  188. quantized/routes/export_facets.py +83 -0
  189. quantized/routes/export_figures.py +471 -0
  190. quantized/routes/export_page.py +125 -0
  191. quantized/routes/fitting.py +379 -0
  192. quantized/routes/fitting_bumps.py +97 -0
  193. quantized/routes/import_template.py +97 -0
  194. quantized/routes/import_wizard.py +150 -0
  195. quantized/routes/jobs_api.py +59 -0
  196. quantized/routes/magnetic.py +135 -0
  197. quantized/routes/magnetometry.py +133 -0
  198. quantized/routes/optics.py +98 -0
  199. quantized/routes/parsers.py +281 -0
  200. quantized/routes/peaks.py +184 -0
  201. quantized/routes/plot.py +103 -0
  202. quantized/routes/reductions.py +121 -0
  203. quantized/routes/reference.py +58 -0
  204. quantized/routes/reflectivity.py +91 -0
  205. quantized/routes/report_export.py +119 -0
  206. quantized/routes/rsm.py +136 -0
  207. quantized/routes/samples.py +32 -0
  208. quantized/routes/semiconductor.py +207 -0
  209. quantized/routes/sld.py +42 -0
  210. quantized/routes/spectral.py +54 -0
  211. quantized/routes/statplots.py +99 -0
  212. quantized/routes/stats.py +418 -0
  213. quantized/routes/stats_design.py +321 -0
  214. quantized/routes/substrates.py +46 -0
  215. quantized/routes/superconductor.py +139 -0
  216. quantized/routes/thermal.py +57 -0
  217. quantized/routes/thin_film.py +153 -0
  218. quantized/routes/vacuum.py +113 -0
  219. quantized/routes/xray.py +32 -0
  220. quantized/samples/demo_vsm.csv +42 -0
  221. quantized/server_launch.py +251 -0
  222. quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  223. quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  224. quantized/web/assets/index-BHmmCL-x.js +27 -0
  225. quantized/web/assets/index-BiZzN7J6.css +1 -0
  226. quantized/web/index.html +13 -0
  227. quantized/web/loading.html +69 -0
  228. quantized_lab-0.8.0.dist-info/METADATA +122 -0
  229. quantized_lab-0.8.0.dist-info/RECORD +233 -0
  230. quantized_lab-0.8.0.dist-info/WHEEL +4 -0
  231. quantized_lab-0.8.0.dist-info/entry_points.txt +4 -0
  232. quantized_lab-0.8.0.dist-info/licenses/LICENSE +201 -0
  233. quantized_lab-0.8.0.dist-info/licenses/NOTICE +11 -0
@@ -0,0 +1,418 @@
1
+ """``.opju`` (CPYUA) real-corpus-form axis record decoding (item 33).
2
+
3
+ Split out of ``figures_opju.py`` to stay under the repo's 500-line
4
+ god-module ceiling; this is purely an extraction, not a new subsystem — see
5
+ ``figures_opju.py``'s module docstring for the full "Real-corpus form"
6
+ narrative (record grammar, value-token encodings) this implements:
7
+
8
+ ```
9
+ 03 00 00 1f layer anchor
10
+ [optional flag token] 1-2 bytes, skipped (see below)
11
+ [X from] [X to] [X step] value tokens; ``from`` elided when 0.0
12
+ 81 <id> <plen> 00 00 01 … separator (layer geometry; plen VARIES
13
+ and is only a search-window hint)
14
+ [Y from] [Y to] [Y step] value tokens (tagged/RLE only)
15
+ 81 <id> <plen> 00 00 01 … end separator
16
+ ```
17
+
18
+ Value tokens: **tagged compact** (``8T nn <nn bytes>``, tag ``0x81..0x8f``,
19
+ payload = the double's BE top-``nn`` reversed), **RLE-compressed literal**
20
+ (a byte-run inside the 8 LE double bytes collapses to a ``c2``/``c3``
21
+ escape — ``c2`` = a run of exactly 5 repeated bytes, ``c3`` = exactly 6; the
22
+ byte after the repeated byte is a context/tag byte, skipped, not a count),
23
+ and **bare compact** (1-3 significant bytes, no tag, right after a flag
24
+ token). The optional X flag token (``89 01``/``89 18``/``97 03``/``91 09`` =
25
+ 2 bytes; a bare ``91`` before a run-first RLE value = 1 byte; absent when
26
+ the record opens with a tagged value) is skipped via a deterministic length
27
+ rule; its semantics stay undecoded.
28
+
29
+ **Y-axis scale flag — solved 2026-07-04** (the rf_* 4-file by-construction
30
+ oracle: ``rf_linlin``/``rf_logx``/``rf_logy``/``rf_loglog.opju``, the SAME
31
+ single-curve graph with identical custom ranges ``x=[0.2,20]``/
32
+ ``y=[50,2000]``, differing only in ``layer.x.type``/``layer.y.type``). The
33
+ end separator's geometry payload is followed by a fixed 4-byte layer-style
34
+ marker ``00 10 10 00``; the 2 bytes immediately before it are an exact Y
35
+ lin/log flag -- ``01 00`` linear, ``08 01`` log10 -- independent of the
36
+ geometry payload's own (variable) shape/length and of X's own type/encoding
37
+ (which shifts the marker's absolute position but never the flag's value or
38
+ its relationship to the marker). Validated exact against all 14 real-corpus
39
+ anchors (RockingCurve's 3 log-Y layers read ``08 01``; XAS 3 + UnpolPlots 4
40
+ + "Fixed Lambdas SI" 4, all linear-Y, read ``01 00``) -- see
41
+ ``docs/origin_project_format.md`` §6.2 for the full byte-level trail. This
42
+ mirrors the independently-discovered ``.opj`` flag (``figure_layers.py``'s
43
+ ``_y_scale_flag``, same two byte values, different fixed offset) -- strong
44
+ cross-container corroboration that both are real, not coincidental.
45
+
46
+ **X-axis scale flag — solved 2026-07-06** (the same rf_* oracle, byte-diffed
47
+ pairwise: ``rf_logx`` vs ``rf_linlin`` and ``rf_loglog`` vs ``rf_logy`` differ
48
+ ONLY in X): the geometry payload between the two separators ENDS with an
49
+ X-scale field carrying the very same two byte values as the Y flag --
50
+ ``01`` = linear, ``08 01`` = log10 -- immediately before the Y span's first
51
+ token, with 0-2 trailing ``00`` pad bytes in between (pad count varies with
52
+ the Y token encoding; the flag bytes never do). ``_real_x_log_flag`` reads it
53
+ by scanning backward from the decoded Y-span start over the ``00`` pads.
54
+ Corpus-proof (see ``docs/origin_re/ORIGIN_CONVENTIONS.md`` §6.2): all 9
55
+ by-construction specimens read exactly (rf quad, fig_linx/logx/xylog via the
56
+ same bytes on the specimen path, ``axis_custom``), the real-corpus log-x
57
+ graph ``Fixed Lambdas SI!Graph6`` (2 layers, GT ``layer.x.type=2``, a 3.8x
58
+ span the decade heuristic mislabels linear) reads ``08 01``, and every
59
+ GT-linear real record (~70 across Hc2/RockingCurve/XAS/UnpolPlots/Fixed
60
+ Lambdas) reads ``01`` -- zero false positives. Six Hc2 records end ``02``
61
+ instead (an unrecognized value, possibly another scale type): those return
62
+ ``None`` and keep the decade heuristic -- never guessed. (The ``85 02 f0
63
+ 3f`` sequence once suspected to be a scale flag is in fact a tagged
64
+ ``y_from = 1.0``; the real flag sits before the Y span, not inside it.)
65
+
66
+ **Panel-layer records (``03 00 00 5f`` anchor) + bare-literal Y spans.**
67
+ Multi-layer panel/composite windows (Fixed Lambdas SI ``Graph5``/``Graph6``,
68
+ RockingCurve/UnpolPlots ``Graph3``) anchor their per-layer records with
69
+ ``03 00 00 5f`` (= ``1f | 0x40``), not ``1f`` -- same record grammar inside.
70
+ Fixed Lambdas' panel layers additionally encode their Y spans as bare 8-byte
71
+ LE literals (elsewhere Y is tagged/RLE only), so the Y scan gets a
72
+ LAST-RESORT bare retry that is accepted ONLY where `_real_x_log_flag`
73
+ authenticates the Y-span start (the exact flag bytes right before it) --
74
+ binary noise cannot satisfy both the flag pattern and a unique exact fill,
75
+ so previously-decoding records parse byte-identically (retry-only, like
76
+ ``_SEP_WIDE``).
77
+ """
78
+
79
+ from __future__ import annotations
80
+
81
+ import re
82
+ import struct
83
+
84
+ __all__: list[str] = [] # internal to the .opju figures subsystem; imported by name
85
+
86
+ # `8x <id> <plen> 00 00 01` separates the X span from the layer-geometry
87
+ # payload and the geometry from the Y span. Two lead bytes exist: `81`
88
+ # (the original form, every record validated through 2026-07-04) and `80`
89
+ # (found 2026-07-05 on Hc2's Graph8/Graph12-family records, always paired
90
+ # with a final span token carrying one trailing subfield byte -- see
91
+ # `_real_tagged_trailer`). The strict `81` form is always tried first and
92
+ # the wide form only as a retry, so previously-decoding records parse
93
+ # byte-identically (see `_parse_real_record`).
94
+ _SEP_STRICT = re.compile(rb"\x81..\x00\x00\x01", re.DOTALL)
95
+ _SEP_WIDE = re.compile(rb"[\x80\x81]..\x00\x00\x01", re.DOTALL)
96
+ # Specimen-form record markers (moved with _parse_specimen_record from
97
+ # figures_opju, 2026-07-06).
98
+ _Y_TRANSITION = bytes([0x81, 0x04, 0x06, 0x00, 0x00, 0x01, 0xC3, 0x66])
99
+ _STEP_TAG = bytes([0x83, 0x02])
100
+
101
+ _TAG_SEARCH_SPAN = 2_000 # max bytes allowed between an anchor and its separator/marker
102
+ # Y may start up to this many bytes past the nominal payload end. 7, not 6:
103
+ # the geometry payload runs 11 bytes past `y_lo` when plen=5 (Hc2 Graph1 /
104
+ # the Graph3 6-curve family, oracle-verified), which the old 6-byte scan
105
+ # missed by exactly one.
106
+ _Y_START_SCAN = 7
107
+
108
+ # Y-scale flag: the fixed layer-style marker that follows the end separator's
109
+ # geometry payload, and the 2-byte flag immediately before it (see module docstring).
110
+ _Y_STYLE_MARKER = bytes([0x00, 0x10, 0x10, 0x00])
111
+ _Y_LIN_FLAG = bytes([0x01, 0x00])
112
+ _Y_LOG_FLAG = bytes([0x08, 0x01])
113
+ _Y_FLAG_SEARCH_SPAN = 150 # observed within ~10-20 bytes in every instance seen
114
+
115
+
116
+ def _plausible(v: float) -> bool:
117
+ return v == v and (v == 0.0 or 1e-9 <= abs(v) <= 1e9)
118
+
119
+
120
+ def _decode_compact(chunk: bytes) -> float | None:
121
+ """1-3 significant bytes: BE top-N of the double, stored reversed."""
122
+ n = len(chunk)
123
+ be = bytes(reversed(chunk)) + b"\x00" * (8 - n)
124
+ v = struct.unpack(">d", be)[0]
125
+ return v if _plausible(v) else None
126
+
127
+
128
+ def _decode_raw8(chunk: bytes) -> float | None:
129
+ if len(chunk) != 8:
130
+ return None
131
+ v = struct.unpack("<d", chunk)[0]
132
+ return v if _plausible(v) else None
133
+
134
+
135
+ def _real_tagged(b: bytes, p: int, end: int) -> tuple[float, int] | None:
136
+ """``8T nn <nn bytes>``: payload reversed is the double's BE top-``nn``."""
137
+ if p + 2 > end:
138
+ return None
139
+ tag, nn = b[p], b[p + 1]
140
+ if not (0x81 <= tag <= 0x8F) or not (1 <= nn <= 8) or p + 2 + nn > end:
141
+ return None
142
+ v = _decode_compact(b[p + 2 : p + 2 + nn])
143
+ return (v, 2 + nn) if v is not None else None
144
+
145
+
146
+ def _real_rle(b: bytes, p: int, end: int) -> tuple[float, int] | None:
147
+ """RLE-compressed 8-byte literal: ``c2`` = run of 5, ``c3`` = run of 6.
148
+
149
+ Lead form (marker at ``p+1``) and run-first form (marker at ``p``); the
150
+ byte after the repeated byte is a context/tag byte and is skipped; literal
151
+ suffix bytes complete the 8-byte LE double (see the module docstring).
152
+ """
153
+ for lead_len in (1, 0):
154
+ mpos = p + lead_len
155
+ if mpos + 3 > end:
156
+ continue
157
+ marker = b[mpos]
158
+ if marker == 0xC2:
159
+ run = 5
160
+ elif marker == 0xC3:
161
+ run = 6
162
+ else:
163
+ continue
164
+ suffix_len = 8 - lead_len - run
165
+ tok_end = mpos + 3 + suffix_len
166
+ if suffix_len < 0 or tok_end > end:
167
+ continue
168
+ raw = b[p : p + lead_len] + bytes([b[mpos + 1]]) * run + b[mpos + 3 : tok_end]
169
+ v = struct.unpack("<d", raw)[0]
170
+ if _plausible(v):
171
+ return (v, tok_end - p)
172
+ return None
173
+
174
+
175
+ def _real_bare8(b: bytes, p: int, end: int) -> tuple[float, int] | None:
176
+ """Bare 8-byte LE literal. A leading byte in the tag range ``0x81..0x8f``
177
+ that failed to decode as a tagged value marks a flag/control position, not
178
+ a literal — no corpus literal starts with such a byte."""
179
+ if p + 8 > end or 0x81 <= b[p] <= 0x8F:
180
+ return None
181
+ v = _decode_raw8(b[p : p + 8])
182
+ return (v, 8) if v is not None else None
183
+
184
+
185
+ def _real_tagged_trailer(b: bytes, p: int, end: int) -> tuple[float, int] | None:
186
+ """``8T nn <nn-1 value bytes> <trailer>`` — a tagged compact whose payload
187
+ carries ONE trailing subfield byte (a small int, ``02``/``04`` observed).
188
+
189
+ Found 2026-07-05 on Hc2's ``80``-lead-separator records: the span's FINAL
190
+ (step) token gains one payload byte and the separator lead flips 81->80
191
+ in lockstep (e.g. ``83 03 14 40 02`` = step 5.0 + trailer 02 on Graph8,
192
+ vs ``83 02 14 40`` on the byte-identical Graph4). Only ever offered for
193
+ the last token of a span (see ``_real_fills``) so it cannot re-split
194
+ ``from``/``to`` values, and the trailer is constrained to a small int.
195
+ """
196
+ if p + 2 > end:
197
+ return None
198
+ tag, nn = b[p], b[p + 1]
199
+ if not (0x81 <= tag <= 0x8F) or not (2 <= nn <= 8) or p + 2 + nn > end:
200
+ return None
201
+ if not 0x01 <= b[p + 2 + nn - 1] <= 0x0F:
202
+ return None
203
+ v = _decode_compact(b[p + 2 : p + 2 + nn - 1])
204
+ return (v, 2 + nn) if v is not None else None
205
+
206
+
207
+ def _real_candidates(
208
+ b: bytes, p: int, end: int, bare: bool, last: bool = False
209
+ ) -> list[tuple[float, int]]:
210
+ out: list[tuple[float, int]] = []
211
+ t = _real_tagged(b, p, end)
212
+ if t is not None:
213
+ out.append(t)
214
+ r = _real_rle(b, p, end)
215
+ if r is not None:
216
+ out.append(r)
217
+ if last:
218
+ tt = _real_tagged_trailer(b, p, end)
219
+ if tt is not None:
220
+ out.append(tt)
221
+ if bare:
222
+ w = _real_bare8(b, p, end)
223
+ if w is not None:
224
+ out.append(w)
225
+ if p < end and not (0x81 <= b[p] <= 0x8F):
226
+ for k in (1, 2, 3): # tag-less compact (seen right after a flag token)
227
+ if p + k <= end:
228
+ v = _decode_compact(b[p : p + k])
229
+ if v is not None:
230
+ out.append((v, k))
231
+ return out
232
+
233
+
234
+ def _real_fills(b: bytes, pos: int, end: int, n: int, bare: bool) -> list[tuple[float, ...]]:
235
+ """All ways to place exactly ``n`` value tokens filling ``[pos, end)``.
236
+
237
+ The final token position (``n == 1``) additionally admits the
238
+ trailing-subfield tagged form (`_real_tagged_trailer`) — the step slot
239
+ only, so ``from``/``to`` decoding is never affected by it."""
240
+ if n == 0:
241
+ return [()] if pos == end else []
242
+ out: list[tuple[float, ...]] = []
243
+ for v, consumed in _real_candidates(b, pos, end, bare, last=n == 1):
244
+ for rest in _real_fills(b, pos + consumed, end, n - 1, bare):
245
+ out.append((v, *rest))
246
+ return out
247
+
248
+
249
+ def real_form_bare_pair(b: bytes, start: int, end: int) -> tuple[float, float] | None:
250
+ """``(from, to)`` from two REAL-FORM value tokens exactly filling
251
+ ``[start, end)`` — the "hybrid" axis record (§13.2 #13): a specimen-form
252
+ skeleton (``_Y_TRANSITION`` marker + ``7b 40`` X-scale filler) whose X/Y
253
+ span nevertheless stores real-corpus RLE/tagged value tokens, produced when
254
+ a plotted graph is later customized (``layer.plotN.*`` edits) instead of
255
+ ranged. An optional 1-2 byte leading flag (``0x81..0x8f``) precedes the
256
+ first value; try each skip and accept only a UNIQUE two-token exact fill
257
+ (fail-closed on ambiguity — never guessed). The tokens themselves are the
258
+ same oracle-validated ``_real_rle``/``_real_tagged`` used by the real form,
259
+ so a hit reproduces Origin's own range (verified: symbol_kinds.opju X span
260
+ ``89 c3 33 03 eb 3f 9a c2 99 02 18 40`` -> (0.85, 6.15), matching COM)."""
261
+ accepted: set[tuple[float, float]] = set()
262
+ for skip in (0, 1, 2):
263
+ if start + skip > end:
264
+ continue
265
+ fills = _real_fills(b, start + skip, end, 2, bare=False)
266
+ pairs = {(f[0], f[1]) for f in fills}
267
+ if len(pairs) == 1:
268
+ accepted |= pairs
269
+ return accepted.pop() if len(accepted) == 1 else None
270
+
271
+
272
+ def _real_span_pair(
273
+ b: bytes, start: int, end: int, bare: bool
274
+ ) -> tuple[float, float, float | None] | None:
275
+ """``[from, to, step]`` (n=3) else ``[to, step]`` with from elided (n=2),
276
+ exact-fill; accepted only when the fill set at that arity is unique on
277
+ the ``(from, to)`` pair (the historical acceptance rule — §13.2 #8 added
278
+ the STEP as a third returned value: the tick increment, ``None`` when
279
+ the accepted fills disagree on it)."""
280
+ for n in (3, 2):
281
+ fills = _real_fills(b, start, end, n, bare)
282
+ pairs = {(f[0], f[1]) if n == 3 else (0.0, f[0]) for f in fills}
283
+ if len(pairs) == 1:
284
+ fr, to = pairs.pop()
285
+ steps = {f[2] if n == 3 else f[1] for f in fills}
286
+ return (fr, to, steps.pop() if len(steps) == 1 else None)
287
+ if len(pairs) > 1:
288
+ return None # ambiguous: drop, never guess
289
+ return None
290
+
291
+
292
+ def _real_x_flag_len(b: bytes, p: int, end: int) -> int:
293
+ """Deterministic length of the optional X flag token (see module docstring):
294
+ 0 when the record opens with a tagged value, 1 for a bare ``91`` before a
295
+ run-first RLE value, else 2 (every other observed flag is 2 bytes)."""
296
+ if _real_tagged(b, p, end) is not None:
297
+ return 0
298
+ if p + 1 < end and b[p] == 0x91 and b[p + 1] in (0xC2, 0xC3):
299
+ return 1
300
+ return 2
301
+
302
+
303
+ def _real_x_log_flag(b: bytes, y_lo: int, y_start: int) -> bool | None:
304
+ """Exact X-axis lin/log flag: the geometry payload's trailing field,
305
+ read backward from the Y-span start over 0-2 ``00`` pad bytes --
306
+ ``01`` = linear, ``08 01`` = log10 (the same byte values as the Y flag;
307
+ see the module docstring for the rf_*-diff derivation and the corpus
308
+ proof). Any other tail returns ``None`` (decade heuristic; e.g. the six
309
+ Hc2 records whose field reads ``02``) -- never guessed."""
310
+ q = y_start
311
+ while q > y_lo and b[q - 1] == 0x00:
312
+ q -= 1
313
+ if q - y_lo >= 2 and b[q - 2 : q] == b"\x08\x01":
314
+ return True
315
+ if q - y_lo >= 1 and b[q - 1] == 0x01 and (q - y_lo < 2 or b[q - 2] != 0x08):
316
+ return False
317
+ return None
318
+
319
+
320
+ def _real_y_log_flag(b: bytes, sep_start: int, window_end: int) -> bool | None:
321
+ """Exact Y-axis lin/log flag for the real-corpus form.
322
+
323
+ Pinned from a 4-file by-construction oracle (``rf_linlin``/``rf_logx``/
324
+ ``rf_logy``/``rf_loglog.opju`` -- the same single-curve graph with
325
+ identical custom ranges, differing only in ``layer.x.type``/
326
+ ``layer.y.type``): the two bytes right before the fixed ``00 10 10 00``
327
+ layer-style marker that follows the end separator are ``01 00`` for a
328
+ linear Y axis and ``08 01`` for log10, regardless of X's own type or
329
+ encoding (which shifts the marker's absolute position but never its
330
+ value or the flag's relationship to it). Validated exact against all 14
331
+ real-corpus anchors (RockingCurve's 3 log-Y layers read ``08 01``; XAS
332
+ 3 + UnpolPlots 4 + "Fixed Lambdas SI" 4, all linear-Y, read ``01 00``).
333
+ Any other value, or no marker found within the search span, returns
334
+ ``None`` so the decade heuristic takes over -- never guessed.
335
+ """
336
+ start = sep_start + 6 # past the separator's own `81 <id> <plen> 00 00 01`
337
+ mpos = b.find(_Y_STYLE_MARKER, start, min(window_end, start + _Y_FLAG_SEARCH_SPAN))
338
+ if mpos < start + 2:
339
+ return None
340
+ flag = b[mpos - 2 : mpos]
341
+ if flag == _Y_LIN_FLAG:
342
+ return False
343
+ if flag == _Y_LOG_FLAG:
344
+ return True
345
+ return None
346
+
347
+
348
+ def _parse_real_record(
349
+ b: bytes, p: int, window_end: int
350
+ ) -> tuple[float, float, float, float, bool | None, bool | None, float | None, float | None] | None:
351
+ """Real-corpus axis record at anchor payload ``p``:
352
+ ``(xf, xt, yf, yt, x_log, y_log, x_step, y_step)`` -- the last two are
353
+ the tick increments (None when undecoded); ``x_log``/``y_log`` are the exact
354
+ flags from ``_real_x_log_flag``/``_real_y_log_flag`` when isolatable,
355
+ else ``None`` (caller falls back to the decade heuristic).
356
+
357
+ Tried with the strict ``81``-lead separator first (every record
358
+ validated through 2026-07-04 parses byte-identically), then retried
359
+ with the wide ``[80|81]`` lead that Hc2's Graph8/Graph12-family records
360
+ need -- a record only ever reaches the wide pass after failing the
361
+ strict one, so the retry can add parses but never change one."""
362
+ for sep_re in (_SEP_STRICT, _SEP_WIDE):
363
+ got = _parse_real_record_sep(b, p, window_end, sep_re)
364
+ if got is not None:
365
+ return got
366
+ return None
367
+
368
+
369
+ def _parse_real_record_sep(
370
+ b: bytes, p: int, window_end: int, sep_re: re.Pattern[bytes]
371
+ ) -> tuple[float, float, float, float, bool | None, bool | None, float | None, float | None] | None:
372
+ """One separator-form attempt of `_parse_real_record` (see above)."""
373
+ m1 = sep_re.search(b, p, min(window_end, p + _TAG_SEARCH_SPAN))
374
+ if m1 is None:
375
+ return None
376
+ x_start = p + _real_x_flag_len(b, p, m1.start())
377
+ if x_start >= m1.start():
378
+ return None
379
+ xpair = _real_span_pair(b, x_start, m1.start(), bare=True)
380
+ if xpair is None:
381
+ return None
382
+ plen = b[m1.start() + 2]
383
+ y_lo = m1.start() + 6
384
+ m2 = sep_re.search(b, y_lo + plen, min(window_end, y_lo + plen + _TAG_SEARCH_SPAN))
385
+ y_hi = m2.start() if m2 else min(window_end, y_lo + plen + 200)
386
+ y_scan_hi = min(y_lo + plen + _Y_START_SCAN, y_hi)
387
+ # plen is a hint only: Y can start inside or a few bytes past the nominal
388
+ # geometry payload, so scan for the first uniquely exact-filling start.
389
+ # Bare 8-byte literals are excluded from the first pass (a mis-aligned
390
+ # scan start could decode plausible junk) and retried last-resort ONLY
391
+ # where the exact X-scale flag authenticates the Y-span start (the
392
+ # Fixed Lambdas panel-layer form -- see the module docstring). In the
393
+ # retry a start pointing AT a ``00`` pad byte is not a candidate: the
394
+ # true span begins at the first non-``00`` after the flag, and letting a
395
+ # bare literal absorb the pads decodes a wrong-but-plausible value
396
+ # (measured: Fixed Lambdas Graph5 layer 2's y_from, -0.0488.. for -0.05).
397
+ for bare in (False, True):
398
+ for y_start in range(y_lo, y_scan_hi):
399
+ if bare and (
400
+ (y_start < y_hi and b[y_start] == 0x00)
401
+ or _real_x_log_flag(b, y_lo, y_start) is None
402
+ ):
403
+ continue
404
+ ypair = _real_span_pair(b, y_start, y_hi, bare=bare)
405
+ if ypair is not None:
406
+ x_log = _real_x_log_flag(b, y_lo, y_start)
407
+ y_log = _real_y_log_flag(b, m2.start(), window_end) if m2 else None
408
+ return (
409
+ xpair[0], xpair[1], ypair[0], ypair[1], x_log, y_log,
410
+ xpair[2], ypair[2],
411
+ )
412
+ return None
413
+
414
+
415
+ # Specimen-form + hybrid record parsing now lives in
416
+ # ``opju_axis_specimen_form.py`` (2026-07-06, the 500-line guard); it imports
417
+ # the value-token readers above. ``_parse_real_record`` here remains the
418
+ # real-corpus-form entry point.
@@ -0,0 +1,167 @@
1
+ """Specimen-form + hybrid Origin ``.opju`` axis-record parsing.
2
+
3
+ Split from ``opju_axis_real_form.py`` (2026-07-06, the 500-line god-module
4
+ guard). Same axis-record domain as the real form: this file owns the
5
+ DEFAULT-DIALOG ("specimen") record grammar and the LAST-RESORT hybrid parser,
6
+ while ``opju_axis_real_form`` owns the real-corpus token grammar and the
7
+ real-form record parser. The two share the low-level value-token readers
8
+ (imported below) so a decode stays byte-identical whichever entry point runs.
9
+
10
+ Three record forms, tried in this order by ``figures_opju``:
11
+
12
+ 1. specimen form (``_parse_specimen_record``) — the default plot-dialog
13
+ layout: literal / tag-compact value tokens around a ``_Y_TRANSITION``
14
+ marker, with an exact ``7b 40``-family X-scale flag;
15
+ 2. real-corpus form (``opju_axis_real_form._parse_real_record``) — RLE/tagged
16
+ tokens with ``81/80`` separators and geometry-tail scale flags;
17
+ 3. hybrid (``parse_hybrid_record``) — a specimen skeleton whose spans hold
18
+ real-form value tokens (a plotted-then-``layer.plotN.*``-customized graph).
19
+ Gated behind BOTH 1 and 2 failing, so it can never change a record they
20
+ already decode; fail-closed on any ambiguous span (never a guessed range).
21
+
22
+ Pure leaf: bytes in → floats out. No fastapi/pydantic/routes imports.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from quantized.io.origin_project.opju_axis_real_form import (
28
+ _STEP_TAG,
29
+ _TAG_SEARCH_SPAN,
30
+ _Y_TRANSITION,
31
+ _decode_compact,
32
+ _decode_raw8,
33
+ real_form_bare_pair,
34
+ )
35
+
36
+ __all__ = ["parse_hybrid_record"]
37
+
38
+
39
+ def _value_candidates(b: bytes, pos: int, end: int) -> list[tuple[float, int]]:
40
+ """Every plausible ``(value, bytes_consumed)`` parse starting at ``pos``.
41
+
42
+ The bare (no-tag) raw8 shape is rejected when ``pos`` itself starts with a
43
+ byte in the real-form flag-token range ``0x81..0x8f`` (mirroring
44
+ ``_real_bare8``'s identical guard): a genuine specimen-form literal never
45
+ starts there, but a real-form flag token (e.g. ``89 01`` before an
46
+ RLE-compressed value) does, and would otherwise misdecode as a plausible-
47
+ looking bare double -- the false positive that made the rf_* oracle
48
+ quad's linear-X records (whose 8 leading bytes are flag+RLE, not a
49
+ literal) parse via the specimen path with a wrong ``x_from`` and a
50
+ type-byte reading that (unlike the true real-form flag) carries no Y
51
+ information at all."""
52
+ avail = end - pos
53
+ out: list[tuple[float, int]] = []
54
+ if avail >= 8 and not (pos < end and 0x81 <= b[pos] <= 0x8F):
55
+ v = _decode_raw8(b[pos : pos + 8])
56
+ if v is not None:
57
+ out.append((v, 8))
58
+ if avail >= 10:
59
+ v = _decode_raw8(b[pos + 2 : pos + 10])
60
+ if v is not None:
61
+ out.append((v, 10))
62
+ for k in (1, 2, 3):
63
+ if avail >= 2 + k:
64
+ v = _decode_compact(b[pos + 2 : pos + 2 + k])
65
+ if v is not None:
66
+ out.append((v, 2 + k))
67
+ return out
68
+
69
+
70
+ def _parse_pair(b: bytes, pos: int, end: int) -> tuple[float, float] | None:
71
+ """Decode ``(from, to)`` from the byte span ``[pos, end)``, or ``None``.
72
+
73
+ Every admissible split (``from`` elided, or ``from``+``to`` both present)
74
+ is tried; accepted only if exactly one split consumes the span exactly with
75
+ two plausible values.
76
+ """
77
+ candidates: set[tuple[float, float]] = set()
78
+ for v, n in _value_candidates(b, pos, end): # from elided (== 0.0): one token = "to"
79
+ if pos + n == end:
80
+ candidates.add((0.0, v))
81
+ for vf, nf in _value_candidates(b, pos, end): # from present, then to
82
+ p2 = pos + nf
83
+ for vt, nt in _value_candidates(b, p2, end):
84
+ if p2 + nt == end:
85
+ candidates.add((vf, vt))
86
+ return candidates.pop() if len(candidates) == 1 else None
87
+
88
+
89
+ def _parse_specimen_record(
90
+ b: bytes, p: int
91
+ ) -> tuple[float, float, float, float, int, bool | None] | None:
92
+ """Specimen-form axis record at anchor payload ``p``:
93
+ ``(xf, xt, yf, yt, type_byte, x_log)``.
94
+
95
+ ``x_log`` is the exact X-scale flag inside the "filler" after the type
96
+ byte -- really ``7b 40`` + ``01`` (linear) / ``08 01`` (log10), the same
97
+ field the real form carries; ``None`` keeps the type-byte/heuristic path.
98
+ ``y_start`` stays at the historical +3 skip: a log X's extra ``08`` byte
99
+ is absorbed by ``_parse_pair``'s 2-byte tag-skip candidate."""
100
+ ytrans = b.find(_Y_TRANSITION, p, min(len(b), p + _TAG_SEARCH_SPAN))
101
+ if ytrans < 0:
102
+ return None
103
+ xstep = b.rfind(_STEP_TAG, p, ytrans)
104
+ if xstep < 0:
105
+ return None
106
+ xpair = _parse_pair(b, p, xstep)
107
+ if xpair is None:
108
+ return None
109
+ if ytrans + len(_Y_TRANSITION) >= len(b): # marker at EOF — no type byte to read
110
+ return None
111
+ tb = ytrans + len(_Y_TRANSITION)
112
+ type_byte = b[tb]
113
+ x_log: bool | None = None
114
+ if b[tb + 1 : tb + 4] == b"\x7b\x40\x01":
115
+ x_log = False
116
+ elif b[tb + 1 : tb + 5] == b"\x7b\x40\x08\x01":
117
+ x_log = True
118
+ y_start = tb + 1 + 3 # + type byte + "7b 40 ..." filler (see docstring)
119
+ ystep = b.find(_STEP_TAG, y_start, min(len(b), y_start + _TAG_SEARCH_SPAN))
120
+ if ystep < 0:
121
+ return None
122
+ ypair = _parse_pair(b, y_start, ystep)
123
+ if ypair is None:
124
+ return None
125
+ return (*xpair, *ypair, type_byte, x_log)
126
+
127
+
128
+ def parse_hybrid_record(
129
+ b: bytes, p: int
130
+ ) -> tuple[float, float, float, float, int, bool | None] | None:
131
+ """LAST-RESORT axis record: ``(xf, xt, yf, yt, type_byte, x_log)``.
132
+
133
+ Fires ONLY when neither the specimen form nor the real form parsed (§13.2
134
+ #13). Shape: a specimen-form skeleton (``_Y_TRANSITION`` + ``_STEP_TAG``
135
+ markers, ``7b 40``-family X-scale filler) whose X and Y spans hold
136
+ real-corpus RLE/tagged value tokens instead of specimen-form literals --
137
+ what a plotted-then-customized (``layer.plotN.*``) graph writes. Reuses the
138
+ specimen skeleton for span boundaries and the type byte / ``7b 40`` filler
139
+ for the scale flag, but decodes each span with the oracle-validated
140
+ real-form token machinery (``real_form_bare_pair``). Because it is gated
141
+ behind BOTH other parsers failing, it can never change a record they
142
+ already decode. Fail-closed: any span that doesn't yield a unique pair
143
+ returns ``None`` (never a guessed range)."""
144
+ ytrans = b.find(_Y_TRANSITION, p, min(len(b), p + _TAG_SEARCH_SPAN))
145
+ if ytrans < 0 or ytrans + len(_Y_TRANSITION) >= len(b):
146
+ return None
147
+ xstep = b.rfind(_STEP_TAG, p, ytrans)
148
+ if xstep < 0:
149
+ return None
150
+ xpair = real_form_bare_pair(b, p, xstep)
151
+ if xpair is None:
152
+ return None
153
+ tb = ytrans + len(_Y_TRANSITION)
154
+ type_byte = b[tb]
155
+ x_log: bool | None = None
156
+ if b[tb + 1 : tb + 4] == b"\x7b\x40\x01":
157
+ x_log = False
158
+ elif b[tb + 1 : tb + 5] == b"\x7b\x40\x08\x01":
159
+ x_log = True
160
+ y_start = tb + 1 + 3
161
+ ystep = b.find(_STEP_TAG, y_start, min(len(b), y_start + _TAG_SEARCH_SPAN))
162
+ if ystep < 0:
163
+ return None
164
+ ypair = real_form_bare_pair(b, y_start, ystep)
165
+ if ypair is None:
166
+ return None
167
+ return (*xpair, *ypair, type_byte, x_log)