qubasic 0.17.0__tar.gz → 0.18.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 (76) hide show
  1. {qubasic-0.17.0 → qubasic-0.18.0}/CHANGELOG.md +8 -0
  2. {qubasic-0.17.0/qubasic.egg-info → qubasic-0.18.0}/PKG-INFO +14 -1
  3. {qubasic-0.17.0 → qubasic-0.18.0}/README.md +13 -0
  4. {qubasic-0.17.0 → qubasic-0.18.0}/pyproject.toml +1 -1
  5. {qubasic-0.17.0 → qubasic-0.18.0/qubasic.egg-info}/PKG-INFO +14 -1
  6. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/__init__.py +1 -1
  7. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/algos2.py +4 -0
  8. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/classic.py +14 -0
  9. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/cli.py +2 -0
  10. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/control_flow.py +2 -1
  11. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/display.py +28 -12
  12. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/file_io.py +5 -1
  13. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/parser.py +5 -1
  14. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/patterns.py +2 -0
  15. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/profiler.py +1 -1
  16. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/statements.py +4 -0
  17. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/sweep.py +2 -2
  18. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/terminal.py +12 -4
  19. {qubasic-0.17.0 → qubasic-0.18.0}/tests/test_golden.py +50 -0
  20. {qubasic-0.17.0 → qubasic-0.18.0}/LICENSE +0 -0
  21. {qubasic-0.17.0 → qubasic-0.18.0}/MANIFEST.in +0 -0
  22. {qubasic-0.17.0 → qubasic-0.18.0}/examples/bell.qb +0 -0
  23. {qubasic-0.17.0 → qubasic-0.18.0}/examples/grover3.qb +0 -0
  24. {qubasic-0.17.0 → qubasic-0.18.0}/examples/locc_teleport.qb +0 -0
  25. {qubasic-0.17.0 → qubasic-0.18.0}/examples/sweep_rx.qb +0 -0
  26. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic.egg-info/SOURCES.txt +0 -0
  27. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic.egg-info/dependency_links.txt +0 -0
  28. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic.egg-info/entry_points.txt +0 -0
  29. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic.egg-info/requires.txt +0 -0
  30. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic.egg-info/top_level.txt +0 -0
  31. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/__main__.py +0 -0
  32. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/algorithms.py +0 -0
  33. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/analysis.py +0 -0
  34. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/backend.py +0 -0
  35. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/benchmarking.py +0 -0
  36. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/bosonic.py +0 -0
  37. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/debug.py +0 -0
  38. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/demos.py +0 -0
  39. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/dynamics.py +0 -0
  40. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/engine.py +0 -0
  41. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/engine_state.py +0 -0
  42. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/errors.py +0 -0
  43. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/exec_context.py +0 -0
  44. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/executor.py +0 -0
  45. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/expression.py +0 -0
  46. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/gates.py +0 -0
  47. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/help_text.py +0 -0
  48. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/io_protocol.py +0 -0
  49. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/jupyter_kernel.py +0 -0
  50. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/locc.py +0 -0
  51. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/locc_commands.py +0 -0
  52. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/locc_display.py +0 -0
  53. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/locc_engine.py +0 -0
  54. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/locc_execution.py +0 -0
  55. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/logical.py +0 -0
  56. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/memory.py +0 -0
  57. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/mock_backend.py +0 -0
  58. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/noise_mixin.py +0 -0
  59. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/pauliprop.py +0 -0
  60. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/program_mgmt.py +0 -0
  61. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/protocol.py +0 -0
  62. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/qchem.py +0 -0
  63. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/qec.py +0 -0
  64. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/qec2.py +0 -0
  65. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/qol.py +0 -0
  66. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/qudits.py +0 -0
  67. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/resources.py +0 -0
  68. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/scope.py +0 -0
  69. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/screen.py +0 -0
  70. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/state_display.py +0 -0
  71. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/strings.py +0 -0
  72. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/subs.py +0 -0
  73. {qubasic-0.17.0 → qubasic-0.18.0}/qubasic_core/web_repl.py +0 -0
  74. {qubasic-0.17.0 → qubasic-0.18.0}/setup.cfg +0 -0
  75. {qubasic-0.17.0 → qubasic-0.18.0}/tests/test_features.py +0 -0
  76. {qubasic-0.17.0 → qubasic-0.18.0}/tests/test_qubasic.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0 (2026-07-03)
4
+
5
+ ### Added
6
+ - `OPTION ENDIAN BIG|LITTLE` — bitstring display order, the BASIC-flavored sibling of `OPTION BASE`. BIG shows qubit 0 leftmost (the textbook order) across every displayed bitstring: histograms and their bit-order header, STATE, PROBS, STEP's live statevector, SWEEP lines, STATS, LOCC register and joint histograms (per-register, keeping the A|B|C order), CSV, and the JSON `counts` (whose `bit_order` field records the active convention, as does STATUS). Bitstring-shaped input follows the display, so the `AMPLIFY` target you type is the histogram line you read. Internal counts keys and statevector indexing keep the qiskit little-endian order, so programs that string-match keys are unaffected.
7
+
8
+ ### Changed
9
+ - `SAVE` persists `OPTION BASE` and `OPTION ENDIAN`, so a LOADed program restores its conventions.
10
+
3
11
  ## 0.17.0 (2026-07-02)
4
12
 
5
13
  Hardening from an SMT-verifier triage (touchstone-prover over all 651
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -284,6 +284,8 @@ ERASE data Delete array
284
284
  OPTION BASE 1 Set array index base
285
285
  ```
286
286
 
287
+ (`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
288
+
287
289
  `DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
288
290
 
289
291
  ## Control flow
@@ -396,6 +398,17 @@ DENSITY Density matrix
396
398
 
397
399
  Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
398
400
 
401
+ ```
402
+ OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
403
+ OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
404
+ ```
405
+
406
+ The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
407
+ SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
408
+ field records the active convention) and the bitstring-shaped inputs that
409
+ mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
410
+ keep the qiskit order, so programs that string-match keys are unaffected.
411
+
399
412
  ### Screen modes
400
413
  ```
401
414
  SCREEN 0 Text (default)
@@ -243,6 +243,8 @@ ERASE data Delete array
243
243
  OPTION BASE 1 Set array index base
244
244
  ```
245
245
 
246
+ (`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
247
+
246
248
  `DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
247
249
 
248
250
  ## Control flow
@@ -355,6 +357,17 @@ DENSITY Density matrix
355
357
 
356
358
  Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
357
359
 
360
+ ```
361
+ OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
362
+ OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
363
+ ```
364
+
365
+ The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
366
+ SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
367
+ field records the active convention) and the bitstring-shaped inputs that
368
+ mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
369
+ keep the qiskit order, so programs that string-match keys are unaffected.
370
+
358
371
  ### Screen modes
359
372
  ```
360
373
  SCREEN 0 Text (default)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qubasic"
7
- version = "0.17.0"
7
+ version = "0.18.0"
8
8
  description = "Quantum BASIC Interactive Terminal"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qubasic
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary: Quantum BASIC Interactive Terminal
5
5
  Author-email: "Charles C. Norton" <machineelv@gmail.com>
6
6
  License-Expression: MIT
@@ -284,6 +284,8 @@ ERASE data Delete array
284
284
  OPTION BASE 1 Set array index base
285
285
  ```
286
286
 
287
+ (`OPTION ENDIAN BIG|LITTLE`, the other OPTION toggle, lives under Display.)
288
+
287
289
  `DIM a(n)` is inclusive: it spans indices base..n, so the declared top index is valid. A `DIM`med array enforces its declared bounds on write; an undimensioned array grows on first assignment.
288
290
 
289
291
  ## Control flow
@@ -396,6 +398,17 @@ DENSITY Density matrix
396
398
 
397
399
  Bitstrings are little-endian: qubit 0 is the rightmost character. Histograms print a `q(n-1) ... q1 q0` header so the mapping is explicit.
398
400
 
401
+ ```
402
+ OPTION ENDIAN BIG Display bitstrings big-endian (qubit 0 leftmost, the textbook order)
403
+ OPTION ENDIAN LITTLE Back to the default (qubit 0 rightmost, the qiskit order)
404
+ ```
405
+
406
+ The toggle covers every displayed bitstring (histograms, STATE, PROBS, STEP,
407
+ SWEEP, STATS, LOCC registers, CSV, and the JSON `counts` — whose `bit_order`
408
+ field records the active convention) and the bitstring-shaped inputs that
409
+ mirror the display (`AMPLIFY`). Internal counts keys and statevector indexing
410
+ keep the qiskit order, so programs that string-match keys are unaffected.
411
+
399
412
  ### Screen modes
400
413
  ```
401
414
  SCREEN 0 Text (default)
@@ -28,7 +28,7 @@ __all__ = [
28
28
  'GATE_TABLE', 'GATE_ALIASES',
29
29
  ]
30
30
 
31
- __version__ = '0.17.0'
31
+ __version__ = '0.18.0'
32
32
 
33
33
  def __getattr__(name):
34
34
  """Lazy import heavy modules on first access."""
@@ -160,6 +160,10 @@ class Algorithms2Mixin:
160
160
  n = self.num_qubits
161
161
  if len(target) != n:
162
162
  raise ValueError(f"AMPLIFY target must be {n} bits, got {len(target)}")
163
+ # The target reads in the active display convention, so what you see in
164
+ # the histogram is what you type here; internally it stays q_{n-1}..q0.
165
+ if getattr(self, '_endian_big', False):
166
+ target = target[::-1]
163
167
  # Oracle: phase-flip |target> by X-conjugating the all-ones MCZ.
164
168
  flip = [i for i, b in enumerate(target) if b == '0'] # bitstring is q_{n-1}..q0
165
169
  qubits = list(range(n))
@@ -421,3 +421,17 @@ class ClassicMixin:
421
421
  return None
422
422
  self._option_base = int(m.group(1))
423
423
  return True, ExecResult.ADVANCE
424
+
425
+ def _cf_option_endian(self, stmt: str, *, parsed=None) -> tuple[bool, ExecOutcome] | None:
426
+ """OPTION ENDIAN BIG|LITTLE — bitstring display order (default LITTLE,
427
+ the qiskit convention: qubit 0 rightmost). Display-side only: internal
428
+ counts keys and statevector indexing keep the qiskit order."""
429
+ if parsed is not None:
430
+ self._endian_big = parsed.big
431
+ else:
432
+ from qubasic_core.patterns import RE_OPTION_ENDIAN
433
+ m = RE_OPTION_ENDIAN.match(stmt)
434
+ if not m:
435
+ return None
436
+ self._endian_big = m.group(1).upper() == 'BIG'
437
+ return True, ExecResult.ADVANCE
@@ -80,6 +80,8 @@ _SPEC_STATEMENTS = [
80
80
  ('FUNCTION', 'FUNCTION <name>(<args>)', 'function returning a value (END FUNCTION)'),
81
81
  ('DIM', 'DIM <name>(<size>[,...])', 'declare an array (name$ for strings; inclusive sizing)'),
82
82
  ('REDIM', 'REDIM [PRESERVE] <name>(<size>)', 'resize an array; PRESERVE keeps existing data'),
83
+ ('OPTION', 'OPTION BASE 0|1 | OPTION ENDIAN BIG|LITTLE',
84
+ 'array index base; bitstring display order (default LITTLE, qubit 0 rightmost)'),
83
85
  ]
84
86
 
85
87
 
@@ -17,7 +17,7 @@ from qubasic_core.statements import (
17
17
  DataStmt, ReadStmt, OnGotoStmt, OnGosubStmt,
18
18
  SelectCaseStmt, CaseStmt, EndSelectStmt, ElseStmt, EndIfStmt,
19
19
  DoStmt, LoopStmt, ExitStmt,
20
- SwapStmt, DefFnStmt, OptionBaseStmt,
20
+ SwapStmt, DefFnStmt, OptionBaseStmt, OptionEndianStmt,
21
21
  SubStmt, EndSubStmt, FunctionStmt, EndFunctionStmt, CallStmt,
22
22
  LocalStmt, StaticStmt, SharedStmt,
23
23
  OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt, StopStmt,
@@ -463,6 +463,7 @@ class ControlFlowMixin:
463
463
  SwapStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_swap(st, rv, parsed=p),
464
464
  DefFnStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_def_fn(st, rv, parsed=p),
465
465
  OptionBaseStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_option_base(st, parsed=p),
466
+ OptionEndianStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_option_endian(st, parsed=p),
466
467
  # Handlers defined in subs.py (parsed is keyword-only)
467
468
  SubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_sub(st, sl, ip, parsed=p),
468
469
  EndSubStmt: lambda s, st, p, ls, sl, ip, rv, ef: s._cf_end_sub(st, parsed=p),
@@ -40,22 +40,38 @@ class DisplayMixin:
40
40
  Console(file=buf, highlight=False, force_terminal=force).print(*renderables)
41
41
  self.io.write(buf.getvalue())
42
42
 
43
+ def _bits(self, s: str) -> str:
44
+ """A bitstring in the active display convention.
45
+
46
+ Little-endian (qubit 0 rightmost, the qiskit order) unless
47
+ OPTION ENDIAN BIG reversed it. LOCC joint keys ('0|01|1') reverse
48
+ per register segment, keeping the A|B|C register order.
49
+ """
50
+ if not getattr(self, '_endian_big', False):
51
+ return s
52
+ if '|' in s:
53
+ return '|'.join(seg[::-1] for seg in s.split('|'))
54
+ return s[::-1]
55
+
43
56
  def _bit_order_note(self, display: list) -> str | None:
44
57
  """Human-facing reminder of which bit is which qubit.
45
58
 
46
59
  Bitstrings are little-endian (qubit 0 is the rightmost character), the
47
- most common source of off-by-reverse mistakes when reading a histogram.
48
- When the result covers the whole register the positions are labelled
49
- q(n-1) ... q1 q0; for a measured subset only the convention is shown.
60
+ most common source of off-by-reverse mistakes when reading a histogram,
61
+ unless OPTION ENDIAN BIG flipped the display order. When the result
62
+ covers the whole register the positions are labelled per qubit; for a
63
+ measured subset only the convention is shown.
50
64
  """
51
65
  if not display:
52
66
  return None
67
+ big = getattr(self, '_endian_big', False)
53
68
  nbits = len(display[0][0])
54
69
  nq = getattr(self, 'num_qubits', nbits)
55
70
  if nbits == nq and nbits <= 16:
56
- labels = ' '.join(f'q{i}' for i in range(nbits - 1, -1, -1))
57
- return f" bit order {labels} (qubit 0 = rightmost)"
58
- return " bit order qubit 0 = rightmost bit"
71
+ order = range(nbits) if big else range(nbits - 1, -1, -1)
72
+ labels = ' '.join(f'q{i}' for i in order)
73
+ return f" bit order {labels} (qubit 0 = {'leftmost' if big else 'rightmost'})"
74
+ return f" bit order qubit 0 = {'leftmost' if big else 'rightmost'} bit"
59
75
 
60
76
  def print_histogram(self, counts: dict[str, int]) -> None:
61
77
  """Measurement histogram with optional rich-table formatting."""
@@ -96,7 +112,7 @@ class DisplayMixin:
96
112
  bar = '\u2588' * bar_len
97
113
  color = "green" if pct > 40 else "yellow" if pct > 10 else "dim"
98
114
  table.add_row(
99
- f"|{state}\u27E9",
115
+ f"|{self._bits(state)}\u27E9",
100
116
  str(count),
101
117
  f"{pct:5.1f}%",
102
118
  f"[{color}]{bar}[/{color}]")
@@ -129,7 +145,7 @@ class DisplayMixin:
129
145
  pct = 100 * count / total
130
146
  bar_len = int(HISTOGRAM_BAR_WIDTH * count / max_count)
131
147
  bar = '\u2588' * bar_len
132
- ket = f"|{state}\u27E9"
148
+ ket = f"|{self._bits(state)}\u27E9"
133
149
  # Colorize bar by probability
134
150
  if _theme and sys.stdout.isatty():
135
151
  rst = _theme.get('reset', '')
@@ -162,7 +178,7 @@ class DisplayMixin:
162
178
  count = 0
163
179
  for i, amp in enumerate(sv):
164
180
  if abs(amp) > AMPLITUDE_THRESHOLD:
165
- state = format(i, f'0{n}b')
181
+ state = self._bits(format(i, f'0{n}b'))
166
182
  prob = abs(amp)**2
167
183
  table.add_row(
168
184
  f"|{state}\u27E9",
@@ -186,7 +202,7 @@ class DisplayMixin:
186
202
  count = 0
187
203
  for i, amp in enumerate(sv):
188
204
  if abs(amp) > AMPLITUDE_THRESHOLD:
189
- state = format(i, f'0{n}b')
205
+ state = self._bits(format(i, f'0{n}b'))
190
206
  prob = abs(amp)**2
191
207
  self.io.writeln(f" |{state}\u27E9 {amp.real:+.4f}{amp.imag:+.4f}j "
192
208
  f"(P={prob:.4f})")
@@ -204,7 +220,7 @@ class DisplayMixin:
204
220
  parts = []
205
221
  for i, amp in enumerate(sv):
206
222
  if abs(amp) > AMPLITUDE_THRESHOLD:
207
- state = format(i, f'0{n}b')
223
+ state = self._bits(format(i, f'0{n}b'))
208
224
  if abs(amp.imag) < AMPLITUDE_THRESHOLD:
209
225
  parts.append(f"{amp.real:+.3f}|{state}\u27E9")
210
226
  else:
@@ -221,7 +237,7 @@ class DisplayMixin:
221
237
  for i, amp in enumerate(sv):
222
238
  p = abs(amp)**2
223
239
  if p > AMPLITUDE_THRESHOLD:
224
- state = format(i, f'0{n}b')
240
+ state = self._bits(format(i, f'0{n}b'))
225
241
  probs.append((state, p))
226
242
 
227
243
  probs.sort(key=lambda x: -x[1])
@@ -54,6 +54,10 @@ class FileIOMixin:
54
54
  f.write(f"METHOD {self.sim_device}\n")
55
55
  if getattr(self, '_screen_mode', 0):
56
56
  f.write(f"SCREEN {self._screen_mode}\n")
57
+ if getattr(self, '_option_base', 0):
58
+ f.write(f"OPTION BASE {self._option_base}\n")
59
+ if getattr(self, '_endian_big', False):
60
+ f.write("OPTION ENDIAN BIG\n")
57
61
  if getattr(self, '_seed', None) is not None:
58
62
  f.write(f"SEED {self._seed}\n")
59
63
  if getattr(self, '_noise_spec', None):
@@ -489,7 +493,7 @@ class FileIOMixin:
489
493
  total = sum(self.last_counts.values())
490
494
  lines.append("state,count,probability")
491
495
  for state, count in sorted(self.last_counts.items(), key=lambda x: -x[1]):
492
- lines.append(f"{state},{count},{count/total:.6f}")
496
+ lines.append(f"{self._bits(state)},{count},{count/total:.6f}")
493
497
  if self.last_sv is not None:
494
498
  lines.append("")
495
499
  lines.append("state,amplitude_re,amplitude_im,probability")
@@ -33,7 +33,7 @@ from qubasic_core.statements import (
33
33
  OnGotoStmt, OnGosubStmt, SelectCaseStmt, CaseStmt,
34
34
  CallStmt, SubStmt, FunctionStmt,
35
35
  OnErrorStmt, ResumeStmt, ErrorStmt, AssertStmt,
36
- SwapStmt, DefFnStmt, OptionBaseStmt,
36
+ SwapStmt, DefFnStmt, OptionBaseStmt, OptionEndianStmt,
37
37
  OnMeasureStmt, OnTimerStmt, DataStmt, ReadStmt,
38
38
  LocalStmt, StaticStmt, SharedStmt,
39
39
  LetStmt, LetArrayStmt, LetStrStmt, PrintStmt, PrintUsingStmt,
@@ -223,6 +223,10 @@ def _handle_option(text, raw):
223
223
  m = RE_OPTION_BASE.match(text)
224
224
  if m:
225
225
  return OptionBaseStmt(raw=raw, base=int(m.group(1)))
226
+ from qubasic_core.patterns import RE_OPTION_ENDIAN
227
+ m = RE_OPTION_ENDIAN.match(text)
228
+ if m:
229
+ return OptionEndianStmt(raw=raw, big=m.group(1).upper() == 'BIG')
226
230
  return None
227
231
 
228
232
  def _handle_data(text, raw):
@@ -79,6 +79,7 @@ RE_INPUT_FILE = re.compile(r'INPUT\s+#(\d+)\s*,\s*(\w+\$?)', re.IGNORECASE)
79
79
  RE_LINE_INPUT = re.compile(
80
80
  r'LINE\s+INPUT\s+(?:"([^"]*)"\s*,\s*)?(\w+\$?)', re.IGNORECASE)
81
81
  RE_OPTION_BASE = re.compile(r'OPTION\s+BASE\s+([01])', re.IGNORECASE)
82
+ RE_OPTION_ENDIAN = re.compile(r'OPTION\s+ENDIAN\s+(BIG|LITTLE)', re.IGNORECASE)
82
83
  RE_IMPORT = re.compile(r'IMPORT\s+"?([^"]+)"?', re.IGNORECASE)
83
84
  RE_SAVE_EXPECT = re.compile(r'SAVE_EXPECT\s+(\w+)((?:[\s,]+\d+)*)\s*->\s*(\w+)', re.IGNORECASE)
84
85
  RE_SAVE_PROBS = re.compile(r'SAVE_PROBS\s+([\d\s,]+)\s*->\s*(\w+)', re.IGNORECASE)
@@ -169,6 +170,7 @@ __all__ = [
169
170
  "RE_INPUT_FILE",
170
171
  "RE_LINE_INPUT",
171
172
  "RE_OPTION_BASE",
173
+ "RE_OPTION_ENDIAN",
172
174
  "RE_IMPORT",
173
175
  "RE_SAVE_EXPECT",
174
176
  "RE_SAVE_PROBS",
@@ -186,7 +186,7 @@ class ProfilerMixin:
186
186
  variance = sum((v - mean) ** 2 for v in vals) / len(vals)
187
187
  std = math.sqrt(variance)
188
188
  prob = mean / avg_shots if avg_shots > 0 else 0
189
- self.io.writeln(f" |{state}\u27E9 {mean:>8.1f} {std:>8.2f} {min(vals):>6} {max(vals):>6} {prob:>7.4f}")
189
+ self.io.writeln(f" |{self._bits(state)}\u27E9 {mean:>8.1f} {std:>8.2f} {min(vals):>6} {max(vals):>6} {prob:>7.4f}")
190
190
  self.io.writeln('')
191
191
 
192
192
  def _stats_export_csv(self, path: str) -> None:
@@ -175,6 +175,10 @@ class DefFnStmt(Stmt):
175
175
  class OptionBaseStmt(Stmt):
176
176
  base: int
177
177
 
178
+ @dataclass(frozen=True, slots=True)
179
+ class OptionEndianStmt(Stmt):
180
+ big: bool
181
+
178
182
  @dataclass(frozen=True, slots=True)
179
183
  class OnMeasureStmt(Stmt):
180
184
  target: int
@@ -95,10 +95,10 @@ class SweepMixin:
95
95
  ranked = sorted(counts.items(), key=lambda x: -x[1])
96
96
  top = ranked[0]
97
97
  bar_len = int(30 * top[1] / self.shots)
98
- top2 = f" |{ranked[1][0]}\u27E9={ranked[1][1]}" if len(ranked) > 1 else ""
98
+ top2 = f" |{self._bits(ranked[1][0])}\u27E9={ranked[1][1]}" if len(ranked) > 1 else ""
99
99
  n_unique = len(ranked)
100
100
  bar = '\u2588' * bar_len
101
- self.io.writeln(f" {var}={val:8.4f} |{top[0]}\u27E9 {top[1]:>5}/{self.shots} "
101
+ self.io.writeln(f" {var}={val:8.4f} |{self._bits(top[0])}\u27E9 {top[1]:>5}/{self.shots} "
102
102
  f"{bar}{top2} ({n_unique} states)")
103
103
  sweep_xs.append(val)
104
104
  sweep_ys.append(top[1] / self.shots)
@@ -1165,7 +1165,9 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
1165
1165
  'method': self.sim_method,
1166
1166
  'device': self.sim_device,
1167
1167
  'seed': getattr(self, '_seed', None),
1168
- 'bit_order': 'little-endian (qubit 0 = rightmost bit)',
1168
+ 'bit_order': ('big-endian (qubit 0 = leftmost bit)'
1169
+ if getattr(self, '_endian_big', False)
1170
+ else 'little-endian (qubit 0 = rightmost bit)'),
1169
1171
  'option_base': getattr(self, '_option_base', 0),
1170
1172
  'locc_mode': bool(getattr(self, 'locc_mode', False)),
1171
1173
  'locc_registers': (list(self.locc.names)
@@ -1677,12 +1679,18 @@ class QBasicTerminal(Engine, ExecutorMixin, ExpressionMixin, DisplayMixin, DemoM
1677
1679
  JSON-serializable: counts, qubit/shot config, user variables, the
1678
1680
  statevector (when small enough), and key run-manifest fields.
1679
1681
  """
1682
+ _big = getattr(self, '_endian_big', False)
1683
+ _counts = self.last_counts or {}
1684
+ if _big and _counts:
1685
+ _counts = {k[::-1]: v for k, v in _counts.items()}
1680
1686
  out: dict = {
1681
- 'counts': self.last_counts or {},
1687
+ 'counts': _counts,
1682
1688
  'num_qubits': self.num_qubits,
1689
+ # Bitstring keys follow the active OPTION ENDIAN convention; the
1690
+ # bit_order field is the self-describing record of which one.
1683
1691
  'shots': self.shots,
1684
- # Bitstrings are little-endian: the rightmost character is qubit 0.
1685
- 'bit_order': 'little-endian (qubit 0 = rightmost bit)',
1692
+ 'bit_order': ('big-endian (qubit 0 = leftmost bit)' if _big
1693
+ else 'little-endian (qubit 0 = rightmost bit)'),
1686
1694
  }
1687
1695
  uvars = {k: v for k, v in self.variables.items()
1688
1696
  if not k.startswith('_') and isinstance(v, (int, float, str, bool))}
@@ -204,6 +204,56 @@ class TestAuditRegressions(unittest.TestCase):
204
204
  _, out = run_script(['METHOD statevector', 'QUBITS 100'])
205
205
  self.assertIn('RANGE', out)
206
206
 
207
+ def test_option_endian_big_display(self):
208
+ t, out = run_script([
209
+ 'QUBITS 3',
210
+ 'SHOTS 128',
211
+ 'OPTION ENDIAN BIG',
212
+ '10 X 0',
213
+ '20 MEASURE',
214
+ 'RUN',
215
+ 'STATE',
216
+ ])
217
+ self.assertIn('qubit 0 = leftmost', out)
218
+ self.assertIn('|100', out) # displayed big-endian
219
+ self.assertEqual(dict(t.last_counts), {'001': 128}) # internal keys unchanged
220
+ self.assertEqual(t.result()['counts'], {'100': 128}) # JSON follows the toggle
221
+ self.assertIn('big-endian', t.result()['bit_order'])
222
+
223
+ def test_option_endian_amplify_matches_display(self):
224
+ # Under BIG, the AMPLIFY target reads as displayed: '100' marks the
225
+ # state whose histogram line says |100> (internally |001>).
226
+ t, _ = run_script([
227
+ 'QUBITS 3',
228
+ 'SHOTS 512',
229
+ 'OPTION ENDIAN BIG',
230
+ '10 H 0 : H 1 : H 2',
231
+ '20 AMPLIFY 100',
232
+ '30 AMPLIFY 100',
233
+ '40 MEASURE',
234
+ 'RUN',
235
+ ])
236
+ top = max(t.last_counts, key=t.last_counts.get)
237
+ self.assertEqual(top, '001')
238
+
239
+ def test_option_endian_save_roundtrip(self):
240
+ import os
241
+ import tempfile
242
+ with tempfile.TemporaryDirectory() as td:
243
+ old_cwd = os.getcwd()
244
+ os.chdir(td)
245
+ try:
246
+ run_script([
247
+ 'OPTION ENDIAN BIG',
248
+ '10 H 0',
249
+ '20 MEASURE',
250
+ 'SAVE endian.qb',
251
+ ])
252
+ t2, _ = run_script(['LOAD endian.qb'])
253
+ self.assertTrue(getattr(t2, '_endian_big', False))
254
+ finally:
255
+ os.chdir(old_cwd)
256
+
207
257
 
208
258
  class TestResearchQEC(unittest.TestCase):
209
259
  def test_bb_code_parameters(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes