IR Debugging and Inspection

IR Debugging and Inspection#

This example shows how to inspect the Intermediate Representation (IR) produced by the parser. It is useful when debugging parsing issues, parameter resolution, or operator recognition.

What you’ll learn#

  • How to call latex_to_ir and inspect ir.terms and ir.has_time_dep.

  • How to use IR inspection to diagnose missing parameters and operator parsing.

Source#

  1"""
  2IR debugging and validation cookbook.
  3
  4This example is meant for developers who need to:
  5- Inspect the intermediate representation (IR) produced from LaTeX.
  6- Understand how time dependence is detected.
  7- Diagnose missing-parameter errors before hitting a backend.
  8- See how operator ordering is preserved in the IR.
  9
 10Run functions individually from a REPL; nothing executes on import.
 11"""
 12
 13# flake8: noqa
 14from __future__ import annotations
 15
 16import sys
 17from pathlib import Path
 18from pprint import pprint
 19from typing import Iterable
 20
 21import sympy as sp
 22
 23ROOT = Path(__file__).resolve().parents[1]
 24if str(ROOT) not in sys.path:
 25    sys.path.insert(0, str(ROOT))
 26
 27from latex_parser.backend_utils import collect_parameter_names, validate_required_params
 28from latex_parser.dsl import BosonSpec, HilbertConfig, QubitSpec
 29from latex_parser.ir import HamiltonianIR, latex_to_ir, parse_latex_expr
 30
 31
 32def _print_terms(ir: HamiltonianIR) -> None:
 33    """Helper: pretty-print IR terms."""
 34    print("has_time_dep:", ir.has_time_dep)
 35    for idx, term in enumerate(ir.terms):
 36        print(f"Term {idx}: scalar={term.scalar_expr}")
 37        print("  ops:", term.ops)
 38
 39
 40def inspect_basic_ir() -> None:
 41    """
 42    Parse a simple driven qubit and print IR contents.
 43    """
 44    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
 45    H_latex = r"\frac{\omega_0}{2} \sigma_{z,1} + A \cos(\omega t) \sigma_{x,1}"
 46    ir = latex_to_ir(H_latex, cfg, t_name="t")
 47    _print_terms(ir)
 48
 49
 50def inspect_operator_ordering() -> None:
 51    """
 52    Show that operator ordering is preserved in the IR.
 53
 54    Note: SymPy may reorder internally; the IR preserves the product order
 55    after non-commutative handling in `latex_to_ir`.
 56    """
 57    cfg = HilbertConfig(
 58        qubits=[],
 59        bosons=[BosonSpec(label="a", index=1, cutoff=3)],
 60        customs=[],
 61    )
 62    H_latex = r"a_{1}^{\dagger} a_{1}"
 63    ir = latex_to_ir(H_latex, cfg, t_name="t")
 64    _print_terms(ir)
 65
 66
 67def detect_time_dependence() -> None:
 68    """
 69    Show how time dependence is flagged via scalar free symbols.
 70    """
 71    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
 72    H_td = r"A \cos(\omega t) \sigma_{x,1}"
 73    H_static = r"\frac{\omega_0}{2} \sigma_{z,1}"
 74    ir_td = latex_to_ir(H_td, cfg, t_name="t")
 75    ir_static = latex_to_ir(H_static, cfg, t_name="t")
 76    print("TD term:")
 77    _print_terms(ir_td)
 78    print("Static term:")
 79    _print_terms(ir_static)
 80
 81
 82def validate_params_against_ir() -> None:
 83    """
 84    Demonstrate param collection + validation without running a backend.
 85    """
 86    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
 87    H = r"\omega \sigma_{z,1} + g \cos(\nu t) \sigma_{x,1}"
 88    c_ops = [r"\sqrt{\gamma} \sigma_{-,1}"]
 89    ir_H = latex_to_ir(H, cfg, t_name="t")
 90    time_names = {"t"}
 91    required = collect_parameter_names(ir_H, cfg, time_names)
 92    for c in c_ops:
 93        ir_c = latex_to_ir(c, cfg, t_name="t")
 94        required |= collect_parameter_names(ir_c, cfg, time_names)
 95    print("Required symbols:", sorted(required))
 96    params_ok = {"omega": 1.0, "g": 0.5, "nu": 2.0, "gamma": 0.1}
 97    params_missing = {"omega": 1.0}
 98    validate_required_params(required, params_ok, time_names)
 99    try:
100        validate_required_params(required, params_missing, time_names)
101    except Exception as exc:  # noqa: BLE001 - user-facing demo
102        print("Expected failure:", exc)
103
104
105def parse_latex_directly(exprs: Iterable[str]) -> None:
106    """
107    Show the raw SymPy expressions produced by `parse_latex_expr`.
108    """
109    for latex in exprs:
110        expr = parse_latex_expr(latex)
111        print("LaTeX:", latex)
112        print("SymPy:", expr)
113        print("Free symbols:", [s.name for s in expr.free_symbols])
114        print("-")
115
116
117def explore_symbol_aliases() -> None:
118    """
119    Display how different spellings map to the same SymPy symbol names.
120    """
121    cases = [
122        r"\omega_c",
123        r"\omega_{c}",
124        r"\omega_{ c }",
125        r"\omega_{c} t",
126    ]
127    parse_latex_directly(cases)
128
129
130def inspect_operator_functions() -> None:
131    """
132    Show operator-valued function handling in the IR.
133    """
134    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
135    exprs = [
136        r"\exp(\sigma_{z,1})",
137        r"\cos(\phi) \sigma_{x,1}",  # scalar cos(phi) times operator
138        r"\exp(\sigma_{z,1}) \sigma_{x,1}",  # invalid operator-valued scalar
139    ]
140    valid = []
141    invalid = []
142    for e in exprs:
143        try:
144            ir = latex_to_ir(e, cfg, t_name="t")
145            valid.append((e, ir))
146        except Exception as exc:  # noqa: BLE001 - user-facing demo
147            invalid.append((e, exc))
148    print("Valid expressions:")
149    for e, ir in valid:
150        print(" ", e)
151        _print_terms(ir)
152    print("Invalid expressions:")
153    for e, exc in invalid:
154        print(" ", e, "->", exc)
155
156
157def show_ir_math_ops() -> None:
158    """
159    Apply simple SymPy math to IR scalar parts (e.g., simplify).
160    """
161    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
162    H = r"g (1+1) \sigma_{x,1}"
163    ir = latex_to_ir(H, cfg, t_name="t")
164    _print_terms(ir)
165    simplified = []
166    for term in ir.terms:
167        scalar_simplified = sp.simplify(term.scalar_expr)
168        simplified.append((scalar_simplified, term.ops))
169    print("After SymPy simplify:")
170    pprint(simplified)
171
172
173def explore_time_symbol_overrides() -> None:
174    """
175    Demonstrate adding extra time-like symbols beyond the default t_name.
176    """
177    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
178    H = r"A \cos(\omega s) \sigma_{x,1}"
179    ir = latex_to_ir(H, cfg, t_name="t", time_symbols=("s",))
180    print("Extra time symbol 's' yields has_time_dep =", ir.has_time_dep)
181    _print_terms(ir)
182
183
184def show_expansion_guard() -> None:
185    """
186    Illustrate the expansion guard for large operator sums.
187    """
188    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
189    H = r"( \sigma_{x,1} + \sigma_{y,1} )^3"
190    ir = latex_to_ir(H, cfg, t_name="t")
191    print("Expanded term count:", len(ir.terms))
192    _print_terms(ir)
193
194
195def rescue_merged_time_scalars() -> None:
196    """
197    Show how merged time scalars (omega_{dt}) are rescued into omega_{d} * t.
198    """
199    cfg = HilbertConfig(qubits=[QubitSpec(label="q", index=1)], bosons=[], customs=[])
200    H = r"\omega_{dt} \sigma_{x,1}"
201    ir = latex_to_ir(H, cfg, t_name="t")
202    _print_terms(ir)
203
204
205if __name__ == "__main__":
206    inspect_basic_ir()
207    inspect_operator_ordering()
208    detect_time_dependence()
209    validate_params_against_ir()
210    explore_symbol_aliases()
211    inspect_operator_functions()
212    show_ir_math_ops()
213    explore_time_symbol_overrides()
214    show_expansion_guard()
215    rescue_merged_time_scalars()

Run#

python examples/example_ir_debugging.py

Notes#

  • The IR is the authoritative representation of what the parser understood.

  • Use it to confirm that symbols you expected to be operators are not accidentally treated as scalars, and vice versa.