From 4f34f34ebd4e2df92c08544b8e1cbfcb6ad20070 Mon Sep 17 00:00:00 2001 From: Yi Hong Teoh <39057801+yhteoh@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:02:44 -0400 Subject: [PATCH 01/25] Remove polyfill from mkdocs configuration --- mkdocs.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yaml b/mkdocs.yaml index 96c27862..a8ae9122 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -144,7 +144,6 @@ markdown_extensions: extra_javascript: - javascripts/mathjax.js - - https://polyfill.io/v3/polyfill.min.js?features=es6 - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js extra_css: From 7dc97e767fca4091578f182b7fb4ccc3aa14848e Mon Sep 17 00:00:00 2001 From: yhteoh Date: Wed, 16 Sep 2026 17:23:19 -0400 Subject: [PATCH 02/25] [fix, feat] type checker implementation and implemented dominator tree data analysis --- src/oqd_core/analysis/analog/__init__.py | 14 - src/oqd_core/analysis/analog/semantics.py | 265 ------------------- src/oqd_core/analysis/analog/symbol_table.py | 250 ----------------- src/oqd_core/analysis/analog/type_checker.py | 252 ++++++++++++++++-- src/oqd_core/analysis/analog/types.py | 158 ++++++----- src/oqd_core/analysis/dominator.py | 71 +++++ src/oqd_core/interface/analog/__init__.py | 6 + uv.lock | 2 +- 8 files changed, 399 insertions(+), 619 deletions(-) delete mode 100644 src/oqd_core/analysis/analog/semantics.py delete mode 100644 src/oqd_core/analysis/analog/symbol_table.py create mode 100644 src/oqd_core/analysis/dominator.py diff --git a/src/oqd_core/analysis/analog/__init__.py b/src/oqd_core/analysis/analog/__init__.py index a667d62c..2e727053 100644 --- a/src/oqd_core/analysis/analog/__init__.py +++ b/src/oqd_core/analysis/analog/__init__.py @@ -1,12 +1,4 @@ from .cfg import AnalogCFGBuilder -from .symbol_table import ( - AnalogSymbolError, - AnalogSymbolTable, - AnalogSymbolTableBuilder, - RegisterEnv, - SymbolBinding, - target_dim, -) from .type_checker import AnalogTypeChecker from .types import AnalogTypeError @@ -15,10 +7,4 @@ "AnalogCFGBuilder", "AnalogTypeChecker", "AnalogTypeError", - "AnalogSymbolError", - "AnalogSymbolTable", - "AnalogSymbolTableBuilder", - "SymbolBinding", - "RegisterEnv", - "target_dim", ] diff --git a/src/oqd_core/analysis/analog/semantics.py b/src/oqd_core/analysis/analog/semantics.py deleted file mode 100644 index 18e2dffa..00000000 --- a/src/oqd_core/analysis/analog/semantics.py +++ /dev/null @@ -1,265 +0,0 @@ -# Copyright 2024-2025 Open Quantum Design - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from __future__ import annotations - -from oqd_compiler_infrastructure.lattice import LatticeBottom - -from oqd_core.analysis.analog.types import ( - BIN_SIG_TABLE, - OP_TABLE, - OPMUL_ALLOWED, - AnalogTypeError, - AnalogTypeLattice, - TAnalog, - TBool, - TLatticeValue, - TList, - TMRef, - TMReg, - TOp, - TQRef, - TQReg, - TScalar, - TTarget, - TTargetRef, - TypeEnv, - type_name, -) -from oqd_core.analysis.utils import alias_types -from oqd_core.interface.analog import ( - Access, - AnalogExprSubtypes, - AnalogList, - Bool, - BoolEq, - BoolNot, - BoolNotEq, - Evolve, - Extract, - Initialize, - MathFunc, - MathImag, - MathNum, - MathVar, - Measure, - ModeRegister, - OperatorMul, - PauliI, - PauliX, - PauliY, - PauliZ, - QuantumRegister, -) -from oqd_core.interface.analog.expr import Annihilation, Creation, Identity, Terminal - -######################################################################################## - -EXPR_NODE_TYPES = alias_types(AnalogExprSubtypes) -TERMINAL_NODE_TYPES = alias_types(Terminal) - -MATH_FUNCS = { - "abs", - "sin", - "cos", - "tan", - "exp", - "log", - "sinh", - "cosh", - "tanh", - "atan", - "acos", - "asin", - "atanh", - "asinh", - "acosh", - "heaviside", - "conj", - "real", - "imag", -} - - -class AnalogSemantics: - def __init__(self, value_lattice: AnalogTypeLattice): - self.value_lattice = value_lattice - - def infer_type(self, expr, env: TypeEnv) -> TLatticeValue: - if not isinstance(expr, EXPR_NODE_TYPES): - raise AnalogTypeError(f"Unsupported expression node: {type(expr).__name__}") - - if isinstance(expr, TERMINAL_NODE_TYPES): - if isinstance(expr, (MathNum, MathVar, MathImag)): - return TScalar - if isinstance(expr, Bool): - return TBool - if isinstance( - expr, (PauliI, PauliX, PauliY, PauliZ, Creation, Annihilation, Identity) - ): - return TOp - if isinstance(expr, QuantumRegister): - return TQReg - if isinstance(expr, ModeRegister): - return TMReg - if isinstance(expr, Access): - if expr.name not in env: - raise AnalogTypeError(f"Undefined variable: {expr.name}") - return env[expr.name] - - if isinstance(expr, AnalogList): - if not expr.values: - return TList(elem=LatticeBottom) - - t = self.infer_type(expr.values[0], env) - for v in expr.values[1:]: - t = self.value_lattice.join(t, self.infer_type(v, env)) - return TList(elem=t) - - if isinstance(expr, Extract): - if expr.access.name not in env: - raise AnalogTypeError(f"Undefined variable: {expr.access.name}") - base = env[expr.access.name] - if base is TQReg: - return TQRef - if base is TMReg: - return TMRef - if isinstance(base, TList): - return base.elem - raise AnalogTypeError(f"Cannot index into {type_name(base)}") - - sig = BIN_SIG_TABLE.get(type(expr)) - if sig is not None: - (lreq, rreq), out = sig - t1 = self.infer_type(expr.expr1, env) - t2 = self.infer_type(expr.expr2, env) - if not self.value_lattice.leq(t1, lreq) or not self.value_lattice.leq( - t2, rreq - ): - raise AnalogTypeError( - f"{type(expr).__name__} got {type_name(t1)}, {type_name(t2)} expected {type_name(lreq)}, {type_name(rreq)}" - ) - return out - - sig = OP_TABLE.get(type(expr)) - if sig is not None: - (lreq, rreq), out = sig - t1 = self.infer_type(expr.op1, env) - t2 = self.infer_type(expr.op2, env) - if not self.value_lattice.leq(t1, lreq) or not self.value_lattice.leq( - t2, rreq - ): - raise AnalogTypeError( - f"{type(expr).__name__} got {type_name(t1)}, {type_name(t2)} expected {type_name(lreq)}, {type_name(rreq)}" - ) - return out - - if isinstance(expr, MathFunc): - if expr.func in MATH_FUNCS: - arg = expr.expr - t = self.infer_type(arg, env) - if not self.value_lattice.leq(t, TScalar): - raise AnalogTypeError( - f"{expr.func} expects scalar, got {type_name(t)}" - ) - return TScalar - - if expr.func == "atan2": - arg = expr.expr - if len(arg) != 2: - raise AnalogTypeError("atan2 expects exactly 2 arguments") - t1 = self.infer_type(arg[0], env) - t2 = self.infer_type(arg[1], env) - if not self.value_lattice.leq( - t1, TScalar - ) or not self.value_lattice.leq(t2, TScalar): - raise AnalogTypeError( - f"{expr.func} expects scalar, got {type_name(t1)}, {type_name(t2)}" - ) - return TScalar - - raise AnalogTypeError(f"Unsupported math function: {expr.func}") - - if isinstance(expr, OperatorMul): - t1 = self.infer_type(expr.op1, env) - t2 = self.infer_type(expr.op2, env) - out = OPMUL_ALLOWED.get((t1, t2)) - if out is None: - raise AnalogTypeError( - f"{type(expr).__name__} expects operator or scalar, got {type_name(t1)}, {type_name(t2)}" - ) - return out - - if isinstance(expr, (BoolEq, BoolNotEq)): - t1 = self.infer_type(expr.expr1, env) - t2 = self.infer_type(expr.expr2, env) - if t1 not in (TBool, TScalar) or t2 not in (TBool, TScalar): - raise AnalogTypeError( - f"{type(expr).__name__} expects bool or scalar, got {type_name(t1)}, {type_name(t2)}" - ) - if t1 is not t2: - raise AnalogTypeError( - f"{type(expr).__name__}: got {type_name(t1)} vs {type_name(t2)}" - ) - return TBool - - if isinstance(expr, BoolNot): - t = self.infer_type(expr.expr, env) - if not self.value_lattice.leq(t, TBool): - raise AnalogTypeError( - f"{type(expr).__name__} expects bool, got {type_name(t)}" - ) - return TBool - - if isinstance(expr, (Initialize, Measure)): - t = self.infer_type(expr.targets, env) - if isinstance(t, TList): - if not self.value_lattice.leq(t.elem, TTargetRef): - raise AnalogTypeError( - f"{type(expr).__name__} expects Quantum targets, got {type_name(t)}" - ) - elif not self.value_lattice.leq(t, TTarget): - raise AnalogTypeError( - f"{type(expr).__name__} expects Quantum targets, got {type_name(t)}" - ) - return TAnalog - - if isinstance(expr, Evolve): - target_t = self.infer_type(expr.targets, env) - if isinstance(target_t, TList): - if not self.value_lattice.leq(target_t.elem, TTargetRef): - raise AnalogTypeError( - f"{type(expr).__name__} expects Quantum targets, got {type_name(target_t)}" - ) - elif not self.value_lattice.leq(target_t, TTarget): - raise AnalogTypeError( - f"{type(expr).__name__} expects Quantum targets, got {type_name(target_t)}" - ) - - duration_t = self.infer_type(expr.duration, env) - if not self.value_lattice.leq(duration_t, TScalar): - raise AnalogTypeError( - f"{type(expr).__name__} expects scalar duration, got {type_name(duration_t)}" - ) - - hamiltonian_t = self.infer_type(expr.hamiltonian, env) - if not self.value_lattice.leq(hamiltonian_t, TOp): - raise AnalogTypeError( - f"{type(expr).__name__} expects operator hamiltonian, got {type_name(hamiltonian_t)}" - ) - - return TAnalog - - raise AnalogTypeError(f"Unsupported expression node: {type(expr).__name__}") diff --git a/src/oqd_core/analysis/analog/symbol_table.py b/src/oqd_core/analysis/analog/symbol_table.py deleted file mode 100644 index 8c1e8ca7..00000000 --- a/src/oqd_core/analysis/analog/symbol_table.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2024-2025 Open Quantum Design - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import annotations - -from typing import Iterable, Union - -from oqd_compiler_infrastructure import ( - CFG, - CFGBlock, - DataflowResult, - ForwardDataflowAnalysis, - Lattice, - LatticeBottom, - LatticeTop, - maplattice, -) -from pydantic import BaseModel, ConfigDict - -from oqd_core.analysis.analog.type_checker import AnalogTypeChecker -from oqd_core.analysis.analog.types import ( - TLatticeValue, - TList, - TMRef, - TMReg, - TQRef, - TQReg, - TTargetRef, - TypeEnv, -) -from oqd_core.interface.analog import ( - Access, - AnalogList, - Declaration, - Extract, - ModeRegister, - QuantumRegister, -) - -######################################################################################## - - -class AnalogSymbolError(TypeError): - """Symbol table error class for Analog.""" - - pass - - -class SymbolBinding(BaseModel): - lattice_type: TLatticeValue - target_dim: int - list_elem: SymbolBinding | None = None - model_config = ConfigDict(frozen=True) - - -RegisterEnv = dict[str, SymbolBinding] - - -class AnalogSymbolTable(BaseModel): - in_env: dict[int, RegisterEnv] - - -class SymbolBindingLattice(Lattice[Union[SymbolBinding, type[LatticeTop]]]): - def top(self) -> type[LatticeTop]: - return LatticeTop - - def bottom(self) -> type[LatticeTop]: - return LatticeBottom - - def leq(self, t1, t2) -> bool: - if t1 is LatticeBottom: - return True - if t2 is LatticeTop: - return True - if t1 is LatticeTop: - return t2 is LatticeTop - if t2 is LatticeBottom: - return False - return t1 == t2 - - def join(self, t1, t2): - if t1 is LatticeTop or t2 is LatticeTop: - return LatticeTop - if t1 is LatticeBottom: - return t2 - if t2 is LatticeBottom: - return t1 - if t1 == t2: - return t1 - return LatticeTop - - def meet(self, t1, t2): - if t1 is LatticeBottom or t2 is LatticeBottom: - return LatticeBottom - if t1 is LatticeTop: - return t2 - if t2 is LatticeTop: - return t1 - if t1 == t2: - return t1 - return LatticeBottom - - -def is_target_lattice_type(t: TLatticeValue) -> bool: - if t in (TQReg, TMReg, TQRef, TMRef, TTargetRef): - return True - if isinstance(t, TList): - return is_target_lattice_type(t.elem) - return False - - -def bind_target_value(expr, t: TLatticeValue, env: RegisterEnv) -> SymbolBinding: - - if isinstance(expr, Access): - if expr.name not in env: - raise AnalogSymbolError(f"Undefined variable: {expr.name}") - return env[expr.name] - - if t is TQReg: - return SymbolBinding(lattice_type=TQReg, target_dim=expr.size) - - if t is TMReg: - return SymbolBinding(lattice_type=TMReg, target_dim=expr.size) - - if isinstance(expr, Extract): - if expr.access.name not in env: - raise AnalogSymbolError(f"Undefined variable: {expr.access.name}") - base = env[expr.access.name] - n_dims = base.target_dim - if n_dims > 0: - if expr.index >= n_dims: - raise AnalogSymbolError("Extract index out of range") - return SymbolBinding(lattice_type=TTargetRef, target_dim=1) - raise AnalogSymbolError("Extract index out of range") - - if isinstance(expr, AnalogList): - if not isinstance(t, TList) or not is_target_lattice_type(t): - raise AnalogSymbolError("target list expected") - elem_bindings = [bind_target_value(v, t.elem, env) for v in expr.values] - target_dim = 0 - for binding in elem_bindings: - target_dim += binding.target_dim - return SymbolBinding( - lattice_type=t, - target_dim=target_dim, - list_elem=elem_bindings[0], - ) - raise AnalogSymbolError(f"Unsupported target expression: {type(expr).__name__}") - - -def target_dim(expr, env: RegisterEnv): - if isinstance(expr, Access): - if expr.name not in env: - raise AnalogSymbolError(f"Undefined Variable: {expr.name}") - return env[expr.name].target_dim - - if isinstance(expr, Extract): - if expr.access.name not in env: - raise AnalogSymbolError(f"Undefined Variable: {expr.access.name}") - base = env[expr.access.name] - n_dims = base.target_dim - if n_dims > 0: - if expr.index >= n_dims: - raise AnalogSymbolError("Extract index out of range") - return 1 - raise AnalogSymbolError("Extract index out of range") - - if isinstance(expr, AnalogList): - dim = 0 - for value in expr.values: - new = target_dim(value, env) - dim += new - return dim - - if isinstance(expr, (QuantumRegister, ModeRegister)): - return expr.size - - raise AnalogSymbolError(f"Invalid target: {type(expr).__name__} ") - - -class AnalogSymbolTableBuilder(ForwardDataflowAnalysis[int, CFGBlock, RegisterEnv]): - """Forward dataflow symbol table for register / target dimension checking.""" - - def __init__( - self, - graph: CFG, - type_result: DataflowResult[int, TypeEnv] | None = None, - ) -> None: - if type_result is None: - type_result = AnalogTypeChecker(graph).dataflow_result - self.type_out_states = type_result.out_states - - self.lattice = maplattice(SymbolBindingLattice)() - self.blocks = graph.blocks - - self.dataflow_result = self.analyze(graph, self.merge_symbol_env) - - self.symbol_table = AnalogSymbolTable( - in_env={ - node_id: {} if state is LatticeBottom else dict(state) - for node_id, state in self.dataflow_result.in_states.items() - }, - ) - - def merge_symbol_env(self, states: Iterable[RegisterEnv]) -> RegisterEnv: - states_list = list(states) - if not states_list: - return self.lattice.bottom() - merged = {} if states_list[0] is LatticeBottom else dict(states_list[0]) - for state in states_list[1:]: - if state is LatticeBottom: - continue - for name in set(merged).union(state): - b1 = merged.get(name) - b2 = state.get(name) - if b1 is None: - merged[name] = b2 - elif b2 is None: - continue - elif b1 != b2: - raise AnalogSymbolError( - f"Incompatible register bindings for {name}" - ) - return merged - - def transfer(self, node_id: int, state_in: RegisterEnv) -> RegisterEnv: - env = {} if state_in is LatticeBottom else dict(state_in) - if self.blocks[node_id].preds == [] or self.blocks[node_id].succs == []: - return env - - stmts = self.blocks[node_id].stmts - for stmt in stmts: - if isinstance(stmt, Declaration): - t = self.type_out_states[node_id].get(stmt.name) - if t is not None and is_target_lattice_type(t): - state_out = dict(env) - state_out[stmt.name] = bind_target_value(stmt.value, t, env) - env = state_out - return env diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 9db2557d..cefe44f0 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -15,7 +15,8 @@ from __future__ import annotations -from typing import Dict +from collections import deque +from typing import Dict, List from oqd_compiler_infrastructure import ( CFG, @@ -23,50 +24,255 @@ DataflowResult, ForwardDataflowAnalysis, LatticeBottom, + LatticeTop, maplattice, ) -from oqd_core.analysis.analog.semantics import AnalogSemantics from oqd_core.analysis.analog.types import ( + SUPPORTED_FUNC_SIGNATURES, AnalogTypeError, AnalogTypeLattice, + TAnalog, TBool, + TComplex, + TFloat, + TInt, + TList, + TMRef, + TMReg, + TOp, + TQRef, + TQReg, TypeEnv, ) -from oqd_core.interface.analog import Break, Continue, Declaration +from oqd_core.interface.analog import ( + Access, + AnalogList, + Annihilation, + Bool, + BoolEq, + BoolGreaterThan, + BoolGreaterThanEq, + BoolLessThan, + BoolLessThanEq, + BoolNot, + BoolNotEq, + Break, + Continue, + Creation, + Declaration, + Evolve, + Extract, + Identity, + Initialize, + MathAdd, + MathDiv, + MathFunc, + MathImag, + MathMul, + MathNum, + MathPow, + MathSub, + MathVar, + Measure, + ModeRegister, + OperatorAdd, + OperatorKron, + OperatorMul, + PauliI, + PauliX, + PauliY, + PauliZ, + QuantumRegister, +) + +######################################################################################## class AnalogTypeChecker(ForwardDataflowAnalysis[int, CFGBlock, TypeEnv]): """Forward dataflow type checker over the Control Flow Graph.""" - def __init__(self, graph: CFG) -> None: - self.value_lattice = AnalogTypeLattice() - self.semantics = AnalogSemantics(self.value_lattice) - self.lattice = maplattice(AnalogTypeLattice)() - self.blocks: Dict[int, CFGBlock] = graph.blocks + lattice = maplattice(AnalogTypeLattice)() + + def __init__(self, runtime_var_types={}): + self.runtime_var_types = runtime_var_types + + def _match_single_function_signature(self, signature, func, *args, env: TypeEnv): + sig_args_types, sig_return_type = signature + + if len(args) != len(sig_args_types): + return False, None + + if any([arg_type is LatticeBottom for arg_type in args]): + return False, None + + if all( + [ + self.lattice._element_lattice().leq(arg_type, sig_arg_type) + for arg_type, sig_arg_type in zip(args, sig_args_types) + ] + ): + return True, sig_return_type + + return False, None + + def _match_function_signature(self, func, *args, env: TypeEnv): + signatures = SUPPORTED_FUNC_SIGNATURES[func] + + for sig in signatures: + _match, return_type = self._match_single_function_signature( + sig, func, *args, env=env + ) + + if _match: + return return_type + + raise AnalogTypeError( + f"{func} signature must be one of:\n " + + "\n ".join( + [ + f"({', '.join([f'TList(elem={x.elem.__name__})' if isinstance(x, TList) else x.__name__ for x in sig[0]])}) -> {sig[1].__name__}" + for sig in signatures + ] + ) + ) + + def _infer_function_signature(self, expr, *, env: TypeEnv): + match expr: + case ( + MathAdd() + | MathSub() + | MathMul() + | MathDiv() + | MathPow() + | BoolEq() + | BoolNotEq() + | BoolGreaterThan() + | BoolGreaterThanEq() + | BoolLessThan() + | BoolLessThanEq() + ): + name = expr.__class__.__name__ + args = [expr.expr1, expr.expr2] + + case BoolNot(): + name = expr.__class__.__name__ + args = [expr.expr] + + case MathFunc(): + name = expr.func + args = expr.exprs if isinstance(expr.exprs, list) else [expr] + + case OperatorAdd() | OperatorKron() | OperatorMul(): + name = expr.__class__.__name__ + args = [expr.op1, expr.op2] + + case Evolve(): + name = expr.__class__.__name__ + args = [expr.hamiltonian, expr.duration, expr.targets] - self.dataflow_result: DataflowResult = self.analyze(graph, self.merge_union) + case Initialize() | Measure(): + name = expr.__class__.__name__ + args = [expr.targets] - def transfer(self, node_id: int, state_in: TypeEnv) -> TypeEnv: - env = {} if state_in is LatticeBottom else dict(state_in) - if self.blocks[node_id].preds == [] or self.blocks[node_id].succs == []: - return env + case _: + raise AnalogTypeError(f"Unable to infer type information from {expr}") - stmts = self.blocks[node_id].stmts - t = LatticeBottom + return self._match_function_signature( + name, *[self._infer_type(a, env=env) for a in args], env=env + ) + + def _infer_type(self, expr, *, env: TypeEnv): + match expr: + case Access(): + return TAnalog if env is LatticeTop else env[expr.name] + case MathVar(): + return getattr(self.runtime_var_types, expr.name, TFloat) + case MathImag(): + return TComplex + case MathNum(): + return TInt if isinstance(expr.value, int) else TFloat + case Bool(): + return TBool + case AnalogList(): + return TList(elem=self._infer_type(expr.values[0])) + case QuantumRegister(): + return TQReg + case ModeRegister(): + return TMReg + case Extract() if env[expr.access.name] == TQReg: + return TQRef + case Extract() if env[expr.access.name] == TMReg: + return TMRef + case Extract() if env[expr.access.name] == TList: + return env[expr.access.name].elem + case ( + PauliI() + | PauliX() + | PauliY() + | PauliZ() + | Annihilation() + | Creation() + | Identity() + ): + return TOp + case _: + return self._infer_function_signature(expr, env=env) + + def init_state(self, nodes: List[int]) -> Dict[int, TypeEnv]: + return {node: LatticeTop for node in nodes} + + def analyze(self, graph: CFG) -> DataflowResult[int, TypeEnv]: + nodes = list(graph.nodes()) + boundary = self.init_state(nodes) + result = self.init_state(nodes) + + worklist = deque(nodes) + iterations = 0 + + while worklist: + node = worklist.popleft() + iterations += 1 + + srcs = list(self.sources(graph, node)) + if srcs: + merged_input = self.merge_intersection(result[n] for n in srcs) + else: + merged_input = result[node] + + if not self.lattice.equal(boundary[node], merged_input): + boundary[node] = merged_input + + next_result = self.transfer(graph, node, merged_input) + if self.lattice.equal(result[node], next_result): + continue + + result[node] = next_result + for target in self.targets(graph, node): + if target not in worklist: + worklist.append(target) + + return self.result(boundary, result, iterations) + + def transfer(self, graph: CFG, node_id: int, state_in: TypeEnv) -> TypeEnv: + block = graph[node_id] + + state_out = {} if state_in == LatticeTop else state_in.copy() + + for stmt in block.stmts: + if block.edge_labels: + if self._infer_type(stmt, env=state_out) is not TBool: + raise AnalogTypeError("branch condition must be bool") + continue - for stmt in stmts: if isinstance(stmt, (Break, Continue)): continue if isinstance(stmt, Declaration): - state_out = dict(env) - state_out[stmt.name] = self.semantics.infer_type(stmt.value, env) - env = state_out + state_out[stmt.name] = self._infer_type(stmt.value, env=state_out) + continue - t = self.semantics.infer_type(stmt, env) - if self.blocks[node_id].edge_labels and t is not TBool: - raise AnalogTypeError("branch condition must be bool") + self._infer_type(stmt, env=state_out) - return env + return state_out diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index b318b391..a813dba1 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -24,23 +24,6 @@ ) from pydantic import BaseModel, ConfigDict -from oqd_core.interface.analog import ( - BoolAnd, - BoolGreaterThan, - BoolGreaterThanEq, - BoolLessThan, - BoolLessThanEq, - BoolOr, - MathAdd, - MathDiv, - MathMul, - MathPow, - MathSub, - OperatorAdd, - OperatorKron, - OperatorSub, -) - ######################################################################################## @@ -70,44 +53,46 @@ def type_name(t: TLatticeValue) -> str: return str(t) -class TAnalog(LatticeTop): - pass +class TAnalog(LatticeTop): ... -class TScalar(TAnalog): - pass +class TScalar(TAnalog): ... -class TBool(TAnalog): - pass +class TComplex(TScalar): ... -class TOp(TAnalog): - pass +class TFloat(TComplex): ... -class TTarget(TAnalog): - pass +class TInt(TFloat): ... -class TTargetRef(TTarget): - pass +class TBool(TAnalog): ... -class TQReg(TTarget): - pass +class TOp(TAnalog): ... -class TMReg(TTarget): - pass +class TTarget(TAnalog): ... -class TQRef(TTargetRef): - pass +class TTargetRef(TTarget): ... -class TMRef(TTargetRef): - pass +class TQReg(TTarget): ... + + +class TMReg(TTarget): ... + + +class TQRef(TTargetRef): ... + + +class TMRef(TTargetRef): ... + + +class TNull(TAnalog): ... class AnalogTypeLattice(LatticeBase[TLatticeValue]): @@ -146,33 +131,74 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ######################################################################################## -# Binary expression signature table: node -> ((left_type, right_type), output_type) -BIN_SIG_TABLE = { - MathAdd: ((TScalar, TScalar), TScalar), - MathSub: ((TScalar, TScalar), TScalar), - MathMul: ((TScalar, TScalar), TScalar), - MathDiv: ((TScalar, TScalar), TScalar), - MathPow: ((TScalar, TScalar), TScalar), - BoolAnd: ((TBool, TBool), TBool), - BoolOr: ((TBool, TBool), TBool), - BoolLessThan: ((TScalar, TScalar), TBool), - BoolLessThanEq: ((TScalar, TScalar), TBool), - BoolGreaterThan: ((TScalar, TScalar), TBool), - BoolGreaterThanEq: ((TScalar, TScalar), TBool), -} - - -# Operator expression signatures -OP_TABLE = { - OperatorAdd: ((TOp, TOp), TOp), - OperatorSub: ((TOp, TOp), TOp), - OperatorKron: ((TOp, TOp), TOp), -} - - -# Allowed type pairs for OperatorMul -OPMUL_ALLOWED = { - (TOp, TOp): TOp, - (TOp, TScalar): TOp, - (TScalar, TOp): TOp, +SUPPORTED_FUNC_SIGNATURES = { + "BoolNot": [((TBool,), TBool)], + "BoolEq": [((TScalar, TScalar), TBool)], + "BoolNotEq": [((TScalar, TScalar), TBool)], + "BoolLessThan": [((TScalar, TScalar), TBool)], + "BoolLessThanEq": [((TScalar, TScalar), TBool)], + "BoolGreaterThan": [((TScalar, TScalar), TBool)], + "BoolGreaterThanEq": [((TScalar, TScalar), TBool)], + "MathAdd": [ + ((TInt, TInt), TInt), + ((TFloat, TFloat), TFloat), + ((TComplex, TComplex), TComplex), + ], + "MathSub": [ + ((TInt, TInt), TInt), + ((TFloat, TFloat), TFloat), + ((TComplex, TComplex), TComplex), + ], + "MathMul": [ + ((TInt, TInt), TInt), + ((TFloat, TFloat), TFloat), + ((TComplex, TComplex), TComplex), + ((TScalar, TOp), TOp), + ((TOp, TScalar), TOp), + ], + "MathDiv": [ + ((TInt, TInt), TFloat), + ((TFloat, TFloat), TFloat), + ((TComplex, TComplex), TComplex), + ], + "MathPow": [ + ((TInt, TInt), TInt), + ((TFloat, TFloat), TFloat), + ((TComplex, TComplex), TComplex), + ], + "Evolve": [ + ((TOp, TFloat, TTargetRef), TNull), + ((TOp, TFloat, TTarget), TNull), + ((TOp, TFloat, TList(elem=TTargetRef)), TNull), + ], + "Initialize": [ + ((TTargetRef,), TNull), + ((TTarget,), TNull), + ((TList(elem=TTargetRef),), TNull), + ], + "Measure": [ + ((TTargetRef,), TList(elem=TInt)), + ((TTarget,), TList(elem=TInt)), + ((TList(elem=TTargetRef),), TList(elem=TInt)), + ], + "abs": [((TInt,), TInt), ((TFloat,), TFloat), ((TComplex,), TFloat)], + "sin": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "cos": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "tan": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "exp": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "log": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "sinh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "cosh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "tanh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "atan": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "acos": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "asin": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "atanh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "asinh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "acosh": [((TFloat,), TFloat), ((TComplex,), TComplex)], + "heaviside": [((TFloat,), TInt), ((TInt), TInt)], + "conj": [((TComplex,), TComplex)], + "real": [((TComplex,), TFloat)], + "imag": [((TComplex,), TFloat)], + "atan2": [((TFloat, TFloat), TFloat), ((TComplex, TComplex), TComplex)], } diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py new file mode 100644 index 00000000..626c5de0 --- /dev/null +++ b/src/oqd_core/analysis/dominator.py @@ -0,0 +1,71 @@ +# Copyright 2024-2025 Open Quantum Design + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +######################################################################################## + +from __future__ import annotations + +from collections import deque + +from oqd_compiler_infrastructure import ( + CFGBlock, + ForwardDataflowAnalysis, + LatticeTop, + PowersetLattice, +) +from oqd_compiler_infrastructure.lattice import PowersetValue + +######################################################################################## + + +class DominatorTreeAnalysis(ForwardDataflowAnalysis[int, CFGBlock, PowersetValue]): + lattice = PowersetLattice() + + def init_state(self, nodes): + return {node: {0} if n == 0 else LatticeTop for n, node in enumerate(nodes)} + + def analyze(self, graph): + nodes = list(graph.nodes()) + boundary = self.init_state(nodes) + result = self.init_state(nodes) + + worklist = deque(nodes) + iterations = 0 + + while worklist: + node = worklist.popleft() + iterations += 1 + + srcs = list(self.sources(graph, node)) + if srcs: + merged_input = self.merge_intersection(result[n] for n in srcs) + else: + merged_input = result[node] + + if not self.lattice.equal(boundary[node], merged_input): + boundary[node] = merged_input + + next_result = self.transfer(graph, node, merged_input) + if self.lattice.equal(result[node], next_result): + continue + + result[node] = next_result + for target in self.targets(graph, node): + if target not in worklist: + worklist.append(target) + + return self.result(boundary, result, iterations) + + def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: + return self.lattice.join(state_in, {node_id}) diff --git a/src/oqd_core/interface/analog/__init__.py b/src/oqd_core/interface/analog/__init__.py index aa46a8ef..42f37413 100644 --- a/src/oqd_core/interface/analog/__init__.py +++ b/src/oqd_core/interface/analog/__init__.py @@ -19,6 +19,7 @@ AnalogExpr, AnalogExprSubtypes, AnalogList, + Annihilation, Bool, BoolAnd, BoolEq, @@ -30,8 +31,10 @@ BoolNot, BoolNotEq, BoolOr, + Creation, Evolve, Extract, + Identity, Initialize, MathAdd, MathBinaryOp, @@ -115,4 +118,7 @@ "Measure", "While", "Extract", + "Creation", + "Annihilation", + "Identity", ] diff --git a/uv.lock b/uv.lock index d2bf8132..ced75cb2 100644 --- a/uv.lock +++ b/uv.lock @@ -1819,7 +1819,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure#cf73ae85cf11e174c9a1b20d84a4f12310e37d19" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure#30ae7f520a0bf3249d4101ef72199a95d2ac0599" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From dc7c42a41d603d4314d7d7b709eab4edb3909cb3 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Wed, 16 Sep 2026 19:13:20 -0400 Subject: [PATCH 03/25] [refactor] TList into generic class instead of pydantic model --- pyproject.toml | 2 +- src/oqd_core/analysis/analog/type_checker.py | 18 +- src/oqd_core/analysis/analog/types.py | 66 +++++--- src/oqd_core/analysis/utils.py | 8 +- uv.lock | 164 ++++++++++--------- 5 files changed, 145 insertions(+), 113 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 61edc51a..5cb96d53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,7 @@ select = ["E4", "E7", "E9", "F", "I"] fixable = ["ALL"] [tool.uv.sources] -oqd-compiler-infrastructure = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure" } +oqd-compiler-infrastructure = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure", branch = "fix_maplattice" } [dependency-groups] dev = ["jupyter>=1.1.1", "pre-commit>=4.1.0", "ruff>=0.15.9"] diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index cefe44f0..f598af30 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -16,7 +16,7 @@ from __future__ import annotations from collections import deque -from typing import Dict, List +from typing import Dict, List, _GenericAlias from oqd_compiler_infrastructure import ( CFG, @@ -44,6 +44,7 @@ TQRef, TQReg, TypeEnv, + get_type_name, ) from oqd_core.interface.analog import ( Access, @@ -131,7 +132,9 @@ def _match_function_signature(self, func, *args, env: TypeEnv): f"{func} signature must be one of:\n " + "\n ".join( [ - f"({', '.join([f'TList(elem={x.elem.__name__})' if isinstance(x, TList) else x.__name__ for x in sig[0]])}) -> {sig[1].__name__}" + f"({', '.join([get_type_name(x) for x in sig[0]])})" + " -> " + f"{get_type_name(sig[1])}" for sig in signatures ] ) @@ -183,6 +186,11 @@ def _infer_function_signature(self, expr, *, env: TypeEnv): ) def _infer_type(self, expr, *, env: TypeEnv): + try: + print(env[expr.access.name]) + except: + pass + match expr: case Access(): return TAnalog if env is LatticeTop else env[expr.name] @@ -195,7 +203,7 @@ def _infer_type(self, expr, *, env: TypeEnv): case Bool(): return TBool case AnalogList(): - return TList(elem=self._infer_type(expr.values[0])) + return TList[self._infer_type(expr.values[0], env=env)] case QuantumRegister(): return TQReg case ModeRegister(): @@ -204,8 +212,8 @@ def _infer_type(self, expr, *, env: TypeEnv): return TQRef case Extract() if env[expr.access.name] == TMReg: return TMRef - case Extract() if env[expr.access.name] == TList: - return env[expr.access.name].elem + case Extract() if env[expr.access.name].__origin__ == TList: + return env[expr.access.name].__args__[0] case ( PauliI() | PauliX() diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index a813dba1..1b85fd86 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -15,14 +15,14 @@ from __future__ import annotations -from typing import Union +from typing import Generic, TypeVar, Union, _GenericAlias from oqd_compiler_infrastructure.lattice import ( LatticeBase, - LatticeBottom, LatticeTop, ) -from pydantic import BaseModel, ConfigDict + +from oqd_core.analysis.utils import all_subclasses ######################################################################################## @@ -33,29 +33,24 @@ class AnalogTypeError(TypeError): pass -class TList(LatticeTop, BaseModel): - """Lattice value representing a list.""" - - model_config = ConfigDict(frozen=True) - elem: TLatticeValue +######################################################################################## -TLatticeValue = Union[TList, type[LatticeTop]] -TypeEnv = dict[str, TLatticeValue] +class TLatticeTop(LatticeTop): ... -def type_name(t: TLatticeValue) -> str: - """Format a lattice value into a readable type name for error messages.""" - if isinstance(t, TList): - return f"TList[{type_name(t.elem)}]" - if isinstance(t, type) and issubclass(t, LatticeTop): - return t.__name__ - return str(t) +class TLatticeBottom(TLatticeTop): ... class TAnalog(LatticeTop): ... +LatticeValueTypeVar = TypeVar("LatticeValueTypeVar", bound=TLatticeTop) + + +class TList(TAnalog, Generic[LatticeValueTypeVar]): ... + + class TScalar(TAnalog): ... @@ -95,14 +90,33 @@ class TMRef(TTargetRef): ... class TNull(TAnalog): ... +TLatticeValue = Union[all_subclasses(TLatticeTop)] +TypeEnv = dict[str, TLatticeValue] + + +def get_type_name(value: TLatticeValue): + if issubclass(type(value), _GenericAlias): + return ( + f"{value.__name__}[{','.join(map(lambda x: x.__name__, value.__args__))}]" + ) + + return value.__name__ + + class AnalogTypeLattice(LatticeBase[TLatticeValue]): """Type lattice for analog expressions.""" + def top(self): + return TLatticeTop + + def bottom(self): + return TLatticeBottom + def leq(self, t1: TLatticeValue, t2: TLatticeValue) -> bool: - if t1 is LatticeBottom: + if t1 is TLatticeBottom: return True if isinstance(t1, TList) and isinstance(t2, TList): - return self.leq(t1.elem, t2.elem) + return self.leq(t1.__args__[0], t2.__args__[0]) if isinstance(t1, TList) or isinstance(t2, TList): return False return super().leq(t1, t2) @@ -113,7 +127,7 @@ def join(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: if self.leq(t2, t1): return t1 if isinstance(t1, TList) and isinstance(t2, TList): - return TList(elem=self.join(t1.elem, t2.elem)) + return TList[self.join(t1.__args__[0], t2.__args__[0])] if isinstance(t1, TList) or isinstance(t2, TList): return TAnalog return super().join(t1, t2) @@ -124,7 +138,7 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: if self.leq(t2, t1): return t2 if isinstance(t1, TList) and isinstance(t2, TList): - return TList(elem=self.meet(t1.elem, t2.elem)) + return TList[self.meet(t1.__args__[0], t2.__args__[0])] return super().meet(t1, t2) @@ -169,17 +183,17 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: "Evolve": [ ((TOp, TFloat, TTargetRef), TNull), ((TOp, TFloat, TTarget), TNull), - ((TOp, TFloat, TList(elem=TTargetRef)), TNull), + ((TOp, TFloat, TList[TTargetRef]), TNull), ], "Initialize": [ ((TTargetRef,), TNull), ((TTarget,), TNull), - ((TList(elem=TTargetRef),), TNull), + ((TList[TTargetRef],), TNull), ], "Measure": [ - ((TTargetRef,), TList(elem=TInt)), - ((TTarget,), TList(elem=TInt)), - ((TList(elem=TTargetRef),), TList(elem=TInt)), + ((TTargetRef,), TList[TInt]), + ((TTarget,), TList[TInt]), + ((TList[TTargetRef],), TList[TInt]), ], "abs": [((TInt,), TInt), ((TFloat,), TFloat), ((TComplex,), TFloat)], "sin": [((TFloat,), TFloat), ((TComplex,), TComplex)], diff --git a/src/oqd_core/analysis/utils.py b/src/oqd_core/analysis/utils.py index 27ddc8a7..8ebcd32f 100644 --- a/src/oqd_core/analysis/utils.py +++ b/src/oqd_core/analysis/utils.py @@ -17,7 +17,7 @@ from __future__ import annotations from types import UnionType -from typing import Annotated, Union, get_args, get_origin +from typing import Annotated, Tuple, Union, get_args, get_origin ######################################################################################## @@ -37,3 +37,9 @@ def alias_types(alias: object) -> tuple[type, ...]: if isinstance(alias, type): return (alias,) return () + + +def all_subclasses(cls) -> Tuple[type, ...]: + return set(cls.__subclasses__()).union( + [s for c in cls.__subclasses__() for s in all_subclasses(c)] + ) diff --git a/uv.lock b/uv.lock index ced75cb2..9de31ed0 100644 --- a/uv.lock +++ b/uv.lock @@ -557,31 +557,35 @@ wheels = [ [[package]] name = "debugpy" -version = "1.8.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/f3/6b1d4c71f4cbb5360009f928934a03b42906f28fc7b3f7f35f04e58acead/debugpy-1.8.21-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:8eeab7b5462f683452c57c0126aaa5ec4e974ddb705f39ba87dff8818c8e08f9", size = 2113873, upload-time = "2026-06-01T19:30:37.148Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f2/17c3bf91cebc173bfbf5734cd2669723d0a35c0cf9d2fd2124546efeae83/debugpy-1.8.21-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:0fddfdc130ac6d8bfc0415b0409822fa901c8f310e5c945ac5653a0352532344", size = 3004715, upload-time = "2026-06-01T19:30:38.888Z" }, - { url = "https://files.pythonhosted.org/packages/5a/22/1f8efd80c7b5909e760f9cfd0c9e8681d2d35d532f7c0a40760cd4da4a19/debugpy-1.8.21-cp310-cp310-win32.whl", hash = "sha256:72b5d676c4cbfac3bac5bb01c138a4656e843f93f03ce2a5f4e394ad49fbee73", size = 5303455, upload-time = "2026-06-01T19:30:40.52Z" }, - { url = "https://files.pythonhosted.org/packages/da/ce/54c79abd6cccef92fa7b43d97e3acafedf4d645557267ece05e948b5e4b8/debugpy-1.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a7fe47fd23da57b9e0bec3f4a8ee65a2dc55782455ed7f2141d75ab5d2eaeef5", size = 5331751, upload-time = "2026-06-01T19:30:42.146Z" }, - { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, - { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, - { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, - { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, - { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, - { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, - { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, - { url = "https://files.pythonhosted.org/packages/77/6b/d817e1f8cc77aa055d37fba092e0febfdff40fe652d8d53d4cd7a86ad98d/debugpy-1.8.21-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:13678151fc401e2d68c9880b91e28714f797d40422994572b24560ef80910a88", size = 2477398, upload-time = "2026-06-01T19:30:57.644Z" }, - { url = "https://files.pythonhosted.org/packages/48/57/412421516afc3055fa577516f00beec3d663f9b0ab330639547ae6c57720/debugpy-1.8.21-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:ecbd158386c31ffe71d46f72d44d56e66331ab9b16cad649156d514368f23ab2", size = 3962096, upload-time = "2026-06-01T19:30:59.235Z" }, - { url = "https://files.pythonhosted.org/packages/c1/62/2c616337cf6ba7b07ebbc97f02c6c945a8e2f76b365e33ee809c32ee36d1/debugpy-1.8.21-cp313-cp313-win32.whl", hash = "sha256:2c2ae706dec41d99a9ca1f7ebc987a83e65578363be6f6b3ac9067504917fae1", size = 5336288, upload-time = "2026-06-01T19:31:00.79Z" }, - { url = "https://files.pythonhosted.org/packages/f8/99/9175103392f84c4b1bf7622888cdc68da07f0ff7d9e581266428f6776033/debugpy-1.8.21-cp313-cp313-win_amd64.whl", hash = "sha256:aa648733047443eb1d07682c4ef287d36a54507b643ffdf38b09a3ef002c72a0", size = 5376567, upload-time = "2026-06-01T19:31:02.56Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3d/f4bbb323a548bfab2af3d6b4ffd9bf22636e55956a1285d317a1de643aad/debugpy-1.8.21-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9bb2a685287a2ac9b181cde89edcec64845cb51de7faaa75badb9a698bc24782", size = 2477209, upload-time = "2026-06-01T19:31:04.157Z" }, - { url = "https://files.pythonhosted.org/packages/8c/2d/6e7ec524984a1702777868de49a4c53202bddac2a432a76a093469587750/debugpy-1.8.21-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:3d6922439bf33fd38a3e2c447869ebc7b97da5cd3d329ff1ef9bc06c4903437e", size = 3927115, upload-time = "2026-06-01T19:31:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/97/47/d1aa6d64005a98a9144647d99306b419396f9ad7bf1d73c119e17a81fb4d/debugpy-1.8.21-cp314-cp314-win32.whl", hash = "sha256:15d4963bd5ffa48f0da0947fd06757fa7621945048a14ad7705431566d3c0e7c", size = 5336724, upload-time = "2026-06-01T19:31:07.711Z" }, - { url = "https://files.pythonhosted.org/packages/5f/67/b905b90d163af11878c1af8abafa4a25206335e112e284e413454543a6da/debugpy-1.8.21-cp314-cp314-win_amd64.whl", hash = "sha256:fe0744a12353406de0ae8ccff0d0a4a666f00801a3db8fd04e7a5f761cd520e8", size = 5373803, upload-time = "2026-06-01T19:31:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, +version = "1.8.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/9d/3cb6693342acf96802dba89934a5b8da43c201d764ebd1f2c0514a3d42bd/debugpy-1.8.22.tar.gz", hash = "sha256:e489c7268e1c7b41e13b438d9c533d2a7af73fb59bf8cd30fead8286c1c39c4e", size = 1710877, upload-time = "2026-09-15T21:44:02.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e9/ced401ba1088e1d2b3228dbbcae33e15aa8c08a1368433bedebfa8efcb02/debugpy-1.8.22-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:fa47099176d1d612bef69e9f74658a5687a9f05bd80fb5710f99959bace85735", size = 2128369, upload-time = "2026-09-15T21:44:04.775Z" }, + { url = "https://files.pythonhosted.org/packages/4f/97/5c8e839a7f73e92cd4a733408bb85bb764e829e52a9fb097f99d6e2cbbc0/debugpy-1.8.22-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:7bf29e0d8ce80b100d37fb333e1b193b790d962739f3067796c2b03ffdc9afee", size = 3022081, upload-time = "2026-09-15T21:44:06.382Z" }, + { url = "https://files.pythonhosted.org/packages/11/ed/fbb045d18a8b733a798bb834a03a20cd64f1446206d844b674bb638da6c3/debugpy-1.8.22-cp310-cp310-win32.whl", hash = "sha256:56b877b37ed73f0bf53ba7afc394816ff1eb5d701bae24a24ed9e1ea6f7ce34f", size = 5325298, upload-time = "2026-09-15T21:44:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/82/7f/633160132b80b424c6816391f239682435c6b77cdd58288d04b3443b9f80/debugpy-1.8.22-cp310-cp310-win_amd64.whl", hash = "sha256:1bd0c6df3c68c0a3f71db8baa3780a953abb65537ec4b3bc6b935ad5b3b3d45c", size = 5353598, upload-time = "2026-09-15T21:44:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/26/4a/8c24c588088c622df6dacdc0110e9cc81b44781fe7a408113441e76aa955/debugpy-1.8.22-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:66e4ac3d6e7026e83e7d93d7ee2f51dd4a4e8dff673578d424e60796893e5b2c", size = 2217799, upload-time = "2026-09-15T21:44:11.57Z" }, + { url = "https://files.pythonhosted.org/packages/c7/14/1c9ff33eba51da70a8dfe2277cc13153bb3ffec79282b9acfcdbe1641d30/debugpy-1.8.22-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:192b73e8d53bbd60225220c0943627bf249ac93d0ea3090e23c45f3e0ceb6a35", size = 3074375, upload-time = "2026-09-15T21:44:13.349Z" }, + { url = "https://files.pythonhosted.org/packages/10/e8/1fa16a94af7d8b86c3ca336d7d32b78cb2c475266d0bb889732d2bc50310/debugpy-1.8.22-cp311-cp311-win32.whl", hash = "sha256:745e1800ec2961e5660c1a317c0a20e28c0fef4de36c04f17a21c33f2b37a92a", size = 5258392, upload-time = "2026-09-15T21:44:15.182Z" }, + { url = "https://files.pythonhosted.org/packages/32/1a/c086b883a4561017bfdd83c1bc70bf2b05ab6540ff767dedf207cadab938/debugpy-1.8.22-cp311-cp311-win_amd64.whl", hash = "sha256:1e76339d5510bc17e9181dba9577508afcb21aad5728f1a55ef74d7d97d255f3", size = 5277955, upload-time = "2026-09-15T21:44:16.986Z" }, + { url = "https://files.pythonhosted.org/packages/73/21/5c7f5c66eaeee57c9dadb0bbff22efddc8d99cdbd3d036c806175f1828cc/debugpy-1.8.22-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ad076f4f66cb8acb79e4384d48b1ea60a0b1957aa0b1112d4887ea3a4df60e0", size = 2499375, upload-time = "2026-09-15T21:44:18.478Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ec/7389798843643ea5bc6a08480ba1b855377c70e2ed90189c0a03594e401e/debugpy-1.8.22-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:8a697acec45dbc70d17fb5d9f4f61989fc294d273c69de487fd10cb35fdd75eb", size = 4000941, upload-time = "2026-09-15T21:44:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/20/f4/64ce7a2499550929e08654360b270409eeda3e321b7c27027d9c919aa36b/debugpy-1.8.22-cp312-cp312-win32.whl", hash = "sha256:12bc7f368182b517cf26c76a2393fff65354e365fa1552b6241e66edff17997b", size = 5359040, upload-time = "2026-09-15T21:44:22.218Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fe/e7d3b72874cdd327bda295d1995a01e426857ae9cbaaba74fd997bc3afbc/debugpy-1.8.22-cp312-cp312-win_amd64.whl", hash = "sha256:371a4ba4a5975eb958393903f3254cf983da7b1c7c178b3f987ee427f42515e3", size = 5398304, upload-time = "2026-09-15T21:44:24.119Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/fee6d40f5865aa84e1100c9df1a0f2cf8991776b61f11f433ff277df4415/debugpy-1.8.22-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:c21dd7e1ec22556bb41dc3bf6b86c603e440ec889c4acb27d7206354b2106c54", size = 2493531, upload-time = "2026-09-15T21:44:25.835Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4f/8df91270afbf61fc5ab202951f28451194a7eb093704b26da11cb0ecd308/debugpy-1.8.22-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:c1ffb9953708b648ecf6acd2e5ad2c002bf68785ed618197dd4463a2a7226f39", size = 3994351, upload-time = "2026-09-15T21:44:27.31Z" }, + { url = "https://files.pythonhosted.org/packages/4f/11/2e34a4f21d6155b0c58003fd6fca9819df7d6fe7d7acd83b146b648a57ee/debugpy-1.8.22-cp313-cp313-win32.whl", hash = "sha256:ba810a66b437e3c43ca0ce0892404f010338286263ca8e5108442d76a9485337", size = 5359071, upload-time = "2026-09-15T21:44:29.064Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f5/c2b3e8260388d7b76bc4e5a3a29648a0ca47dca6a2721d6549ff2d944eee/debugpy-1.8.22-cp313-cp313-win_amd64.whl", hash = "sha256:f49b1d6cecf326b63c06d7f517e4a6642777f758c58cb799e7bc5a6dd74721c2", size = 5400362, upload-time = "2026-09-15T21:44:30.857Z" }, + { url = "https://files.pythonhosted.org/packages/ea/95/d97240bb68b86db8d0a07602be068aae9e50f17063d696a4c6f619975311/debugpy-1.8.22-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:e6744ac1850c73c2ba7b29a126cf9ab74efd1831190720e8fee17ef5389c8f5d", size = 2493148, upload-time = "2026-09-15T21:44:32.518Z" }, + { url = "https://files.pythonhosted.org/packages/70/9e/994fe1fb23dd4a5a8d059a0dc95da4b0821907200a61c2d5a4fdc21b6cf8/debugpy-1.8.22-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:0c1104233340196e5cbf5514e7dfdbb98968e730cfd7fdd6f3d72a082be838a9", size = 3961025, upload-time = "2026-09-15T21:44:34.387Z" }, + { url = "https://files.pythonhosted.org/packages/37/e0/66319918883cf6dd6dea3805210c3cafd9e65dfaa6f8b852482cff10a026/debugpy-1.8.22-cp314-cp314-win32.whl", hash = "sha256:feea785c7bbeb8cfd5b01a63f9061c899dc468c8be81671141a29305774fa294", size = 5359408, upload-time = "2026-09-15T21:44:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/94/53/5b4d91f7fffb9393ac907133201d1da0c070d57e463ee8e2ec1db3cb63b6/debugpy-1.8.22-cp314-cp314-win_amd64.whl", hash = "sha256:d593a330297332ec435f3965c448b6d500e03f33675cf2063178bc76377a788e", size = 5397429, upload-time = "2026-09-15T21:44:38.429Z" }, + { url = "https://files.pythonhosted.org/packages/24/71/580df4aed96c9b23c9cbb049907d8ac1347050afdb8f450df20649d51578/debugpy-1.8.22-cp315-cp315-macosx_15_0_universal2.whl", hash = "sha256:a9eca6ab09a61534e923064400081e016f7e1d8430cc6e8b7a9cecd2f59e2b07", size = 2493380, upload-time = "2026-09-15T21:44:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/b0/10/a323945b0966e560c40fe48470d2847bfc7692a8f550d7086e9a28f5b623/debugpy-1.8.22-cp315-cp315-manylinux_2_34_x86_64.whl", hash = "sha256:b7dbde1fb822d100802d505b2aed6d0813b1a0a2015d495cb8798b2215d5d1e5", size = 3975456, upload-time = "2026-09-15T21:44:41.623Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f9/4383635281999c3eaa44bf327de523ba42240b35eb87c1130adac4a829c6/debugpy-1.8.22-cp315-cp315-win32.whl", hash = "sha256:225d063f81708c2546999e7edfac0198b2d5c2f144797dc64858f867948633e0", size = 5359469, upload-time = "2026-09-15T21:44:43.234Z" }, + { url = "https://files.pythonhosted.org/packages/7b/14/4fe9f46a9c825c8cbc6dc4aebc2a885b0bceb37ce2a37699c170482e7a40/debugpy-1.8.22-cp315-cp315-win_amd64.whl", hash = "sha256:b17a4896520f1c6da09ce76ec8215df0b85b0b6f3617526a4c65c2315340875a", size = 5397869, upload-time = "2026-09-15T21:44:44.783Z" }, + { url = "https://files.pythonhosted.org/packages/56/3d/c7dc9f35bc9e22cdb53679f844bee40fd5cff871862f60595869a433400d/debugpy-1.8.22-py2.py3-none-any.whl", hash = "sha256:a9e9d3550e15ca479c59333e90845029190531f0cacfedab3b815a57bd913947", size = 5374857, upload-time = "2026-09-15T21:44:54.454Z" }, ] [[package]] @@ -652,11 +656,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.32.6" +version = "3.32.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/59/e19834834cb01a32febfbb0f8a23a9088088f5d45991824ff2bc3b5e8acb/filelock-3.32.7.tar.gz", hash = "sha256:37b8a3d9811b0f9aef7e5ec5c71bb320de52df51e6ca9bcd6f5ad81187660da7", size = 225154, upload-time = "2026-09-16T00:24:20.907Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/31098c5aeb4d966b553641472bd55fcf5fdfac953549894b8a765ba44e91/filelock-3.32.7-py3-none-any.whl", hash = "sha256:65ff0d0190ea42038b32bda4b77834fb05be2cad4c5b9b01aa4dfb3614536e52", size = 100157, upload-time = "2026-09-16T00:24:19.543Z" }, ] [[package]] @@ -1086,7 +1090,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.21.0" +version = "2.21.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1109,9 +1113,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/e0/63b481d5b21f81fe23173dfc9eb25629fa1bc174fdc4a2c19d09c1ff8124/jupyter_server-2.21.0.tar.gz", hash = "sha256:70d9a1883f57d3576ea17f4ce061ec1a7aad7ef388d00428cfb7f5e4f0022271", size = 760357, upload-time = "2026-08-27T16:06:34.049Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/3d/3c9f8bce5d5448107bd285cd80185e76c9c871d076b580dfd90997e52904/jupyter_server-2.21.1.tar.gz", hash = "sha256:a8960aa29263f6041283e97d4756b099fb49b767baab6371891ecb1bd40a63df", size = 761651, upload-time = "2026-09-15T16:00:07.853Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/8a/cb97c2b353cb46cced413be8f742fa8fcd3cb1659524f27314494aa94968/jupyter_server-2.21.0-py3-none-any.whl", hash = "sha256:2ae2e5ce5e97268553e25aebe040197455673d925b5fc7995477153318160cf7", size = 393961, upload-time = "2026-08-27T16:06:31.814Z" }, + { url = "https://files.pythonhosted.org/packages/1d/39/91bc08650cc8e3efeb7a83c25e20ebddcc9b70d28b4eb6f51a4fb55da7a4/jupyter_server-2.21.1-py3-none-any.whl", hash = "sha256:2a6467606af7dbae2e7e31640030025969e15db6a649eae334af90415dc71dca", size = 394641, upload-time = "2026-09-15T16:00:05.526Z" }, ] [[package]] @@ -1164,7 +1168,7 @@ wheels = [ [[package]] name = "jupyterlab-server" -version = "2.28.0" +version = "2.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -1175,9 +1179,9 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/c5/08946a10b81bd64408d8b78cb352e17fe52d3ee11c2aef00a0f06e506938/jupyterlab_server-2.28.1.tar.gz", hash = "sha256:0c3c2418d51021ce280916e63dfe4cba8386b4e2787be5c25433c98f560ddb31", size = 78141, upload-time = "2026-09-15T19:22:09.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, + { url = "https://files.pythonhosted.org/packages/23/d3/996332fe24f3c2458b9c353b998570dfa2631763e9a8674ba3f94c0877d0/jupyterlab_server-2.28.1-py3-none-any.whl", hash = "sha256:4bd36c7c11d872e15cefa4951e12380a47e6901687165332dd50a007cb367ad9", size = 60197, upload-time = "2026-09-15T19:22:08.201Z" }, ] [[package]] @@ -1819,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure#30ae7f520a0bf3249d4101ef72199a95d2ac0599" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#b551fb33709c73ef94f91a18635d4388fd9d5e66" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, @@ -1888,7 +1892,7 @@ requires-dist = [ { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.0" }, { name = "mkdocstrings-python", marker = "extra == 'docs'" }, { name = "numpy" }, - { name = "oqd-compiler-infrastructure", git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure" }, + { name = "oqd-compiler-infrastructure", git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice" }, { name = "pydantic", specifier = ">=2.10.6" }, { name = "pymdown-extensions", specifier = ">=10.16.1" }, { name = "pymdown-extensions", marker = "extra == 'docs'" }, @@ -1974,11 +1978,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.11.8" +version = "4.11.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/18/f3bb8ef0d3b930692343da8aa4d3cbcd6749477c053959395ac81965a6e9/platformdirs-4.11.8.tar.gz", hash = "sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc", size = 37182, upload-time = "2026-09-08T22:20:42.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/b9/8adc4e1b422b27fd88540ec7bf1f406f77ef393ec070e26fc430e914cde8/platformdirs-4.11.9.tar.gz", hash = "sha256:e2c66a8d384596cd98e3c4aea2d761df7bac95d9d8a2cc3946daa8cdafdaebc1", size = 38345, upload-time = "2026-09-16T13:31:45.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/e1/5b7b8bbb55084d1425bcb9bc823ff519e1b2be05f6ebb0089e2eacc38413/platformdirs-4.11.8-py3-none-any.whl", hash = "sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081", size = 24027, upload-time = "2026-09-08T22:20:41.537Z" }, + { url = "https://files.pythonhosted.org/packages/f3/94/803ba86705257d7eedddac4b02eb88a7483b1600e9200c1fefc6f1a9a3ff/platformdirs-4.11.9-py3-none-any.whl", hash = "sha256:0a3958f58a9e30321eaef0a424dd0b77cce242886b36b8aa992f9731ef2d59c1", size = 24472, upload-time = "2026-09-16T13:31:44.049Z" }, ] [[package]] @@ -2224,15 +2228,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "11.0.2" +version = "12.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ad/17/2db4b414de89659144488e0d9c6c0bf0c8395841dc12d81d0532cc6ef310/pymdown_extensions-11.0.2.tar.gz", hash = "sha256:9506fcbe66fa355a775b768084334238dd6805020ac4b92bea0c0dda6f8f223d", size = 855419, upload-time = "2026-08-22T19:28:47.236Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/7e/53144b512818c75d459523e3a91d9bf7b65cd6a1dfb964fe690adc8b3670/pymdown_extensions-12.0.1.tar.gz", hash = "sha256:88fe2c97fb9153b9f2cc1da59f1d7c7cb74971110daf33cccb2a5514e0f7e970", size = 866904, upload-time = "2026-09-15T23:20:33.187Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/43/9f45ec4d14e596efc32c925a78104934790438b0c0628b70d741016734ad/pymdown_extensions-11.0.2-py3-none-any.whl", hash = "sha256:259910762019732caa1dfd76f3faa62c59f191d46573e80bcb1d13c0f675bbe5", size = 269929, upload-time = "2026-08-22T19:28:45.389Z" }, + { url = "https://files.pythonhosted.org/packages/8c/30/c866b3ca8ad5cca79a3ee1a55c12d61d3e72381e5eadbfdaceb6006ebc84/pymdown_extensions-12.0.1-py3-none-any.whl", hash = "sha256:15d629e8035892b93d788a8424ccea754d4aa3cec247a64cbfe7b15089c9ad7f", size = 276957, upload-time = "2026-09-15T23:20:31.551Z" }, ] [[package]] @@ -2795,27 +2799,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.16.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/bb/5a449b9162e49b139d72f61672bd3ac1d790221f796d3304e2241fff4c58/ruff-0.16.7.tar.gz", hash = "sha256:5f71d004ac1263b22fa39462ac5ae618a4b77d58981af2cc79bf79a29c12b1a6", size = 4924184, upload-time = "2026-09-10T18:04:06.336Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/b2/c80aeeb7f9e469c0d63a85d2f1ab6e1ebfbe10ea7a8d2438b7e09e3ff09e/ruff-0.16.7-py3-none-linux_armv6l.whl", hash = "sha256:727307773e7c7f9181d3ed3a2484186e56c1fa1874255911c74585eb2c7c19f9", size = 10048917, upload-time = "2026-09-10T18:03:30.28Z" }, - { url = "https://files.pythonhosted.org/packages/7b/96/20bb7bcae008004df52afcb7ac83432d4a467f2c17b672fe46d26be231c5/ruff-0.16.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9d61c258deabf58f34c67bd4bb4d939c7f2e6b5f0e59c1cdd1cf771b11cde929", size = 10242929, upload-time = "2026-09-10T18:03:32.706Z" }, - { url = "https://files.pythonhosted.org/packages/90/b2/f184b0d5abec02db69cfd7e49b688ae0237554528ca777136c613bf36bee/ruff-0.16.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ab81118df8945e0193d0240712aa4496573595b75185c3636ed825592a0f728", size = 9847245, upload-time = "2026-09-10T18:03:34.509Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2d/db1633a641866ed801e34cc6b60ef236c5e16f9b2124ab1d49cc24a5fe4f/ruff-0.16.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c196c968874fc8019da8e7163de7a1a370f111e2309b4b7dfea0fce950198d0", size = 9961780, upload-time = "2026-09-10T18:03:36.618Z" }, - { url = "https://files.pythonhosted.org/packages/4d/98/edea21e1a3e38dbbc3bf6bb068b863b3b06184cf8533a4c7dbbe208a89d5/ruff-0.16.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac8c3bd0a7e10ad31e6ce51e7a99f3cb772e69aecdd6b9ea7e99b362f62a62c0", size = 9866337, upload-time = "2026-09-10T18:03:38.805Z" }, - { url = "https://files.pythonhosted.org/packages/0b/11/a15e60d4c87b214646f116ca9d204475bf993ee1047459bc9a360fd4d6d1/ruff-0.16.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398d3988edde000b5c75dc1b3f584708da9bc990de069c18909142580fec1af9", size = 10562512, upload-time = "2026-09-10T18:03:40.71Z" }, - { url = "https://files.pythonhosted.org/packages/29/42/eaff4c9b6d0c7cdf56df313a17e89ae854f5bbc0b0c8f9cce19be0ab7a8f/ruff-0.16.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce05b62b770a8217c4646a9c4139fca00efe8fe5d71f87df2b243ff20d4584d1", size = 11302938, upload-time = "2026-09-10T18:03:42.607Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/c75aa59a4ec181fe2ec06cab30e198c1c6d107229a9f008ae3a7c16cabd8/ruff-0.16.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af1b576fddb9d9ef2ececfb5fadcd6a624b25070ed85e3cfcfe449fc3ff6a7b9", size = 10840857, upload-time = "2026-09-10T18:03:44.604Z" }, - { url = "https://files.pythonhosted.org/packages/21/33/81f3da371942ea031105ba679d8d6e28ec1660ccd690a45f42d381161356/ruff-0.16.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce7f8f22df67c93ed96c717f9128eadb797144ac2bad475cf536f31d6100c55", size = 10370001, upload-time = "2026-09-10T18:03:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0b/6345fb4dbf6dd0ed1cfe5d18391dc9c3f59cc81622a7b0a65b84b3e730ba/ruff-0.16.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:06d0e93d04f392996435ebd600c153f65b47d73fbec2415aa99c5ee5756b3a5f", size = 10548735, upload-time = "2026-09-10T18:03:48.658Z" }, - { url = "https://files.pythonhosted.org/packages/3f/4d/c5576adf511f92a328e5569dda190ecdd430da51f1a649f3a4a2fd73e21e/ruff-0.16.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:142151a5e7b93c1b11111337142f89dd2fbfee92161225c99a97222f22e32656", size = 10108496, upload-time = "2026-09-10T18:03:50.563Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8c/667d83c16199a17a56adc6b0bd4c3beb5b767a2babcd16a56f76f9be7fd6/ruff-0.16.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e6651f97a342d8b35d54d8991544ca22169b86dc54111cb604666940c431b750", size = 9860136, upload-time = "2026-09-10T18:03:52.621Z" }, - { url = "https://files.pythonhosted.org/packages/99/75/78d401106731999a1dd20cc5a6961e37e1eb9397a3b589f73f3a5ce146a3/ruff-0.16.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ef140c6eb935fa9a84c9c607dfb2cb1b85843c192e79265b0c54f35f557ea8e5", size = 10286290, upload-time = "2026-09-10T18:03:55.207Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/56f9c3a8b755df93a0ad318b2147bf4ef5dae9a7e5ec61c460109c67957f/ruff-0.16.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53e39506a730fadeee0d998ed5946f30671f0db240c6c7c73bdabbe33604bb6f", size = 10745048, upload-time = "2026-09-10T18:03:57.299Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ea/7f9b938a63ece4bec677ad7f9f7fa02df3383db1949ed93a382441c09a87/ruff-0.16.7-py3-none-win32.whl", hash = "sha256:2ea3470fcebcbc5df2fb0c6f3b90333fa9084c534e0111c038fa4a6ab9f1c4b7", size = 10059082, upload-time = "2026-09-10T18:03:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/39/11/480a6973a927aa653e1cead6a6416008640e03a99d05b34c0434b8c6c366/ruff-0.16.7-py3-none-win_amd64.whl", hash = "sha256:7ac26aca826e9e21d0f1cb25b54ac660760a9fdd094d3e4df9848232be98cfc6", size = 10593368, upload-time = "2026-09-10T18:04:01.999Z" }, - { url = "https://files.pythonhosted.org/packages/8b/4b/51327018d056f0dad2c2238f26d1fb0f53707a9d91b75dea6d1b3039f136/ruff-0.16.7-py3-none-win_arm64.whl", hash = "sha256:aab7f39e2c9df6c596216070f98eef1207b94f8516cca20c808826974971855b", size = 10412401, upload-time = "2026-09-10T18:04:04.098Z" }, +version = "0.16.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/78/449cb84790bd5cc3823b2652ee405a4558856e5c4195aee3a16bf7b3eb5d/ruff-0.16.8.tar.gz", hash = "sha256:9247bf92b5f04d825c8639a4fe423ec2e4222acd9222e58412b0dab7e442798b", size = 4938814, upload-time = "2026-09-16T15:54:46.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/25/6071aabc530e9be7e2c195e8fe3f7aea2735405b6cf447212832d7811831/ruff-0.16.8-py3-none-linux_armv6l.whl", hash = "sha256:6ffbd6d87383c1edf5f6fa890f10200950240d7c1a16052a19a09d3a2307dd38", size = 10048966, upload-time = "2026-09-16T15:53:57.605Z" }, + { url = "https://files.pythonhosted.org/packages/54/98/07f90ecbc74dd5fb5764f11f2bc774d6a7cffef92d2ff5f5b4e9e23c754e/ruff-0.16.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:42ed6b878ed61e3acca92f2730a17acff39286944ea82398544696366a6f925e", size = 10165498, upload-time = "2026-09-16T15:54:01.14Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1f/e6a712e3b47cad4a40600134105ed193cb773f618a42eb7ba323cb812cc0/ruff-0.16.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ea781c7f2afba8c6a505ea0fb3f994020249e0c450635f5381286fea6b46170", size = 9830004, upload-time = "2026-09-16T15:54:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/23/f2/311a08776d75d81c7676e20b6b020ae63cbe881fcdc7a8dd64e6e18bdd93/ruff-0.16.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8efeae3bbe414a5efefda11a792dfb51ef90ac48d50c4830de2f644caf3e8659", size = 9986558, upload-time = "2026-09-16T15:54:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ed/37b6cb3d3ba8c73e68ae3eb1d502383beb5aa05a582bb7bb3a922f929f54/ruff-0.16.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a79b795469fef7fc6e908b218eed2eb17332afd85031db6480dc864560e69b2", size = 9877332, upload-time = "2026-09-16T15:54:09.552Z" }, + { url = "https://files.pythonhosted.org/packages/22/cc/40873a8f36ad084cc540d55fcca7077264d5b13b24659e9180c176fb2b08/ruff-0.16.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fdc5563cdc50555e6fba39322850860e9267c1b3d12c26a74729d8604c3c812", size = 10507125, upload-time = "2026-09-16T15:54:12.152Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/fc91a642b78ccbab6b9477720f3644ae7a10a9bcce69a934679cd64f62bc/ruff-0.16.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34508983c70665578dab88f5223d8e6228307e1135398ca8bfc8b7e9501e282b", size = 11336694, upload-time = "2026-09-16T15:54:15.489Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/bbd2a9a600a4e73dc3e7548a249c8d1671273464b55822c6fae50f602dff/ruff-0.16.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:644bb578569e0ffc575741232bd385dacdd6fbe123f1a729e7a225f54aa3957f", size = 10774448, upload-time = "2026-09-16T15:54:18.16Z" }, + { url = "https://files.pythonhosted.org/packages/1a/41/d83af9879a7b6e8bf5fe16b1da0b134049d2f5d3afac12defb0897cb84bd/ruff-0.16.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e7d226246961db9235098333caa13063906d3851136b84c2900b82f5daa1df", size = 10323796, upload-time = "2026-09-16T15:54:20.743Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2c/cefd07bfe914b84943ea769ade8d607bd22750b965d3228eefd7cebd15d0/ruff-0.16.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a2bf6bc3e9ebdd4449abc6f06cf64b98051a2c61cf94d2fe9596518c881f1a1e", size = 10514115, upload-time = "2026-09-16T15:54:23.497Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/76a2e26c79a23be6e6e3664c57bec9e9fc8de155cfb9e4b67ea91b64f9d7/ruff-0.16.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ca111ba0849539165e9e59d2b442542f3c1e8060ebbdea82494f1ffbccb1e1f", size = 10072582, upload-time = "2026-09-16T15:54:26.185Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/f42edddb39668af1a559ceafa3823aedd65633a48dc9768e775485faa2c1/ruff-0.16.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:359a1e5b495448ee1e91018064382ebc86f90e8aac2fed222c7d0e4e8df85fd2", size = 9879644, upload-time = "2026-09-16T15:54:29.278Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/913e3195d95e0378786c6656945c865f534a3560e29139da4882aff630d1/ruff-0.16.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:59e8f5681349474110b24d62e93cfda6593f5fa3473446ca3705200cac1a08b9", size = 10231569, upload-time = "2026-09-16T15:54:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/8aa6ea0bdcedbd1bf87397e2fc4ed8406448ea5842f8660bc6e5f163039d/ruff-0.16.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:efa3e7a16d1baaa79957888dfdf8be9ef2e44db81cb032af06d76632ab59e773", size = 10663666, upload-time = "2026-09-16T15:54:34.838Z" }, + { url = "https://files.pythonhosted.org/packages/3d/02/7f10ef4700bc223c30a3fdd10631a29830c45524b810a3c7ed947af64591/ruff-0.16.8-py3-none-win32.whl", hash = "sha256:55793ba85c69921e89be061426d91a78652d6e50317c962240922747a4eb713f", size = 10093472, upload-time = "2026-09-16T15:54:37.47Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/a509c07d714b6da88f2c518b4637cf6f1d46b074be8f0f1e5fb9ff5126fe/ruff-0.16.8-py3-none-win_amd64.whl", hash = "sha256:a6b85621fd3c81e31fc5f5add09c9c078b430db3595ca632efafdec9e64ebfaa", size = 10586899, upload-time = "2026-09-16T15:54:40.488Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a0/50787329e4f20bf9dc9f6230015d46ec69c51a97ace5bc202dae4755365d/ruff-0.16.8-py3-none-win_arm64.whl", hash = "sha256:d075e820af612102ce217f07cc93e69f9490b10ec13ea85fa87bd03d996cef8a", size = 10386316, upload-time = "2026-09-16T15:54:43.332Z" }, ] [[package]] @@ -3158,19 +3162,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.9" +version = "6.5.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/15/ca7aaebb77f850493cba71ace508aeffb896d1ad8976417b48b79823dbd2/tornado-6.5.9.tar.gz", hash = "sha256:4d868544ebdf2fc155239a74397f65ebfea9b4d3635296c96b5dfb0701c354f5", size = 536145, upload-time = "2026-09-14T18:08:37.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/61/53d562a57b28c08eda40b258c0f975e360541943ad7c7bef897a40caafda/tornado-6.5.10.tar.gz", hash = "sha256:a6b1ccd08c04b4a06fb5aeb381be99de5ad1e5375c1785e31d78c880feb57687", size = 537910, upload-time = "2026-09-15T13:47:48.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/4a/4d3459fd2f360dd99233f69f22b36012a42856ace526da2c6da3cb0e6df2/tornado-6.5.9-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:dea3bee433b73d6312d993ce89ad5f2b8d3ed9fde56e79ba5342b9edb78d342a", size = 464279, upload-time = "2026-09-14T18:08:20.909Z" }, - { url = "https://files.pythonhosted.org/packages/26/4b/5af2f7dcd674e2f1cacef763456f79823404f50954789d6e1ec7a70cd58a/tornado-6.5.9-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:22bed49cdb55292d1d58f46008ba2cc3020e5b84995ef6ae749676c8b5309514", size = 462440, upload-time = "2026-09-14T18:08:22.711Z" }, - { url = "https://files.pythonhosted.org/packages/9e/0f/38f8d16011de3b1ce2babb4c3bcefb126d2dc47d04878ae3ad5c39a00caa/tornado-6.5.9-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cc0876a58eee9143476f24901b1b8eb4d52443283ade2b6bca650dd1da04f84", size = 465496, upload-time = "2026-09-14T18:08:24.407Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e9/3888f265d1e287fc63cd61a6696f96a650024e32f9e89c0b1817ac5c40f4/tornado-6.5.9-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc0d703cc7ec92b47ebfe236c97cd765c1f2e45744a2e156e6677e54c948274d", size = 466468, upload-time = "2026-09-14T18:08:25.928Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/d2bcf417981fa6f7698ab84a52394a76cf608e6704d266e2e0e29f1fd9d0/tornado-6.5.9-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:93f9ab0226ef625b94cacb7981a5af104f1735ea02f92282abe5c389109fb31d", size = 466303, upload-time = "2026-09-14T18:08:27.566Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a5/c5cfab11420c3f908238a6c861b8f3864fdbd2b2b87e30c9c16f36a11512/tornado-6.5.9-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5801bbf94dfde8d21087b6aa0b17f3a5dab6110dfe44e7e4492df5de393ac15b", size = 465706, upload-time = "2026-09-14T18:08:29.182Z" }, - { url = "https://files.pythonhosted.org/packages/ab/36/5a5da0aa15b7afdd46ece09c004a75af7d00f6a96b692bf696ff94bc2a55/tornado-6.5.9-cp39-abi3-win32.whl", hash = "sha256:dca9f377e80efe5dd4d52a2776b67492a81a39b2d931de169b6b36d0c1da444c", size = 466750, upload-time = "2026-09-14T18:08:30.836Z" }, - { url = "https://files.pythonhosted.org/packages/34/d7/2446cf3125372e2b4be6483b4b93f360dc50ce2493864755484b23bb50f2/tornado-6.5.9-cp39-abi3-win_amd64.whl", hash = "sha256:f810730ac71eadf009e6e5cb6d534d096887310f5649cff19d7e0d19c0bf4ee7", size = 467191, upload-time = "2026-09-14T18:08:32.521Z" }, - { url = "https://files.pythonhosted.org/packages/39/70/f1024364bf78fa921c63ef2883d36a3b3b705750c98ae174593afaab7a65/tornado-6.5.9-cp39-abi3-win_arm64.whl", hash = "sha256:b5f4d92798337260c6d9f9f0e400c7539dd5e55cf93479a3f8508c9aa09c2b3b", size = 466211, upload-time = "2026-09-14T18:08:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5b/ff5fc58fa2427c30dea74c90053f4fc5eda1e7f3833ed3ecc7147fe2b311/tornado-6.5.10-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9261783640e23258694a9ff0795df430a5a7b0a651d3dd53dd0969ad6be16da7", size = 465883, upload-time = "2026-09-15T13:47:35.463Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f5/cd7be26c34a3315532f3aef5f092465da8f59c334dd439d3c14aaef16461/tornado-6.5.10-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:83e6cf438b106c6b3852d70960967bb1b70c87438050dca0981e4b9aa751a4c1", size = 464046, upload-time = "2026-09-15T13:47:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/60/33/df6d7d04854a58619f8349a51e3edb138324130a7562b0bb21f115bb940f/tornado-6.5.10-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bdf942448169e5336451d0494d7e3d81cfa726d5aa312affdc4682dd62a62f6d", size = 467096, upload-time = "2026-09-15T13:47:38.559Z" }, + { url = "https://files.pythonhosted.org/packages/29/17/cc35dff68272d685cffd8600ffafbd8067e7d05e7348d9f80caddffbbd5f/tornado-6.5.10-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69acca6501eed74582b76dbbceee2a91613f54728e3e418346000d7103101676", size = 468067, upload-time = "2026-09-15T13:47:40.085Z" }, + { url = "https://files.pythonhosted.org/packages/c3/01/6e5349b4e1a53a4b4972a6716785e1fe7407f312063c3972690af8ff301b/tornado-6.5.10-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:66aaa3f57d30c6e6becee83ff28055d5930ac724214bde99393eefda83d5e015", size = 467901, upload-time = "2026-09-15T13:47:41.576Z" }, + { url = "https://files.pythonhosted.org/packages/28/5e/b4facf94370dba006819c8d304376f8b9fbec6b935b5e51bf45823a9790b/tornado-6.5.10-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4bd192b959f9128fb99b8898148070ba4574c9589b78bce42d1851131fe85828", size = 467308, upload-time = "2026-09-15T13:47:43.145Z" }, + { url = "https://files.pythonhosted.org/packages/56/ae/047938e828cafc8eca4c908fafb6588fee944e3af39a0af9d7b602499ae5/tornado-6.5.10-cp39-abi3-win32.whl", hash = "sha256:302eb1e0e3e159314eb591920529fdea80acca92df5510a2cec5bbd4f099ec72", size = 468387, upload-time = "2026-09-15T13:47:44.556Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d4/5901517f05affd752490f6a654ba31b7474664e8dd80bd045a00c220bd88/tornado-6.5.10-cp39-abi3-win_amd64.whl", hash = "sha256:37ae8f150cecfdbf747fc4e12f5e9a97ecd8cf1d4cdb3f119e2de84b11196918", size = 468828, upload-time = "2026-09-15T13:47:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/fd497f3a7f7b74bb04f4b94536b5c9f80742b5d50501fd27977652ddec16/tornado-6.5.10-cp39-abi3-win_arm64.whl", hash = "sha256:ce045d3c298fddd30e89a2777f97039d1b641eb9518ac7b26a4721903539c694", size = 467847, upload-time = "2026-09-15T13:47:47.283Z" }, ] [[package]] @@ -3238,16 +3242,16 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]] name = "virtualenv" -version = "21.7.9" +version = "21.7.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -3256,9 +3260,9 @@ dependencies = [ { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/5c/ba82fdd0da13ade01453fc08277a70abc79128101319549f55f2e13f8f83/virtualenv-21.7.9.tar.gz", hash = "sha256:a7e42d81d779dec8afd7dc4be71640fb959ea861bccfa5980cb4ad9f92e30675", size = 5348882, upload-time = "2026-09-09T01:03:27.752Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/5acd348310e0803c658c8cf7c4d928e2d22fc4f79c29098b651cd3edfdba/virtualenv-21.7.10.tar.gz", hash = "sha256:a7bf10f37ecc36f1942d6e469d6b59f5fe308f60ac711f66f51ef3bd8cb2c9aa", size = 5350247, upload-time = "2026-09-15T23:36:05.739Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/fc/888c80b8da917bfc48a41887eca244f995d190bd3c01175727089fcaf27e/virtualenv-21.7.9-py3-none-any.whl", hash = "sha256:ba3b0bb41063c848d84d76a9fe3fb7711aaa0f2fe78708e8f3cd9714770e4eac", size = 5324667, upload-time = "2026-09-09T01:03:26.023Z" }, + { url = "https://files.pythonhosted.org/packages/85/7c/b75957b57ef372d84628b1099c4a530b3c361878cc6c54a8dfc5071d371a/virtualenv-21.7.10-py3-none-any.whl", hash = "sha256:d7ac9669ba19e675ffadbb97fe8e887ef92fb1743fe9a0032be62b947657327a", size = 5324900, upload-time = "2026-09-15T23:36:03.751Z" }, ] [[package]] From 95ba0c251e83ae7268c8aa54d2096675cb9e341a Mon Sep 17 00:00:00 2001 From: yhteoh Date: Wed, 16 Sep 2026 19:21:52 -0400 Subject: [PATCH 04/25] [fix] lattice operations for TList --- src/oqd_core/analysis/analog/types.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index 1b85fd86..a6270244 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -103,6 +103,12 @@ def get_type_name(value: TLatticeValue): return value.__name__ +def isTList(value: TLatticeValue): + if issubclass(type(value), _GenericAlias) and value.__origin__ is TList: + return True + return False + + class AnalogTypeLattice(LatticeBase[TLatticeValue]): """Type lattice for analog expressions.""" @@ -115,9 +121,9 @@ def bottom(self): def leq(self, t1: TLatticeValue, t2: TLatticeValue) -> bool: if t1 is TLatticeBottom: return True - if isinstance(t1, TList) and isinstance(t2, TList): + if isTList(t1) and isTList(t2): return self.leq(t1.__args__[0], t2.__args__[0]) - if isinstance(t1, TList) or isinstance(t2, TList): + if isTList(t1) or isTList(t2): return False return super().leq(t1, t2) @@ -126,9 +132,9 @@ def join(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: return t2 if self.leq(t2, t1): return t1 - if isinstance(t1, TList) and isinstance(t2, TList): + if isTList(t1) and isTList(t2): return TList[self.join(t1.__args__[0], t2.__args__[0])] - if isinstance(t1, TList) or isinstance(t2, TList): + if isTList(t1) or isTList(t2): return TAnalog return super().join(t1, t2) @@ -137,7 +143,7 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: return t1 if self.leq(t2, t1): return t2 - if isinstance(t1, TList) and isinstance(t2, TList): + if isTList(t1) and isTList(t2): return TList[self.meet(t1.__args__[0], t2.__args__[0])] return super().meet(t1, t2) From 5c40593487cf0c60506abdb18d10a923dae484cc Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 09:54:55 -0400 Subject: [PATCH 05/25] [refactor] type checker treats modes and qubits the same leaving the matching of modes, qubits and hamitlonian to a different analysis --- src/oqd_core/analysis/analog/type_checker.py | 62 ++++++++++++-------- src/oqd_core/analysis/analog/types.py | 37 ++++-------- uv.lock | 2 +- 3 files changed, 48 insertions(+), 53 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index f598af30..0d9e2f0a 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -12,11 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +######################################################################################## + from __future__ import annotations from collections import deque -from typing import Dict, List, _GenericAlias +from functools import reduce +from typing import Dict, List from oqd_compiler_infrastructure import ( CFG, @@ -38,11 +41,9 @@ TFloat, TInt, TList, - TMRef, - TMReg, TOp, - TQRef, TQReg, + TQRegElem, TypeEnv, get_type_name, ) @@ -117,10 +118,23 @@ def _match_single_function_signature(self, signature, func, *args, env: TypeEnv) return False, None + def _print_function_signature(self, signature): + sig_args_types, sig_return_type = signature + + if sig_return_type: + return ( + f"({', '.join([get_type_name(x) for x in sig_args_types])})" + " -> " + f"{get_type_name(sig_return_type)}" + ) + + return f"({', '.join([get_type_name(x) for x in sig_args_types])})" + def _match_function_signature(self, func, *args, env: TypeEnv): - signatures = SUPPORTED_FUNC_SIGNATURES[func] + signature = (args, None) + supported_signatures = SUPPORTED_FUNC_SIGNATURES[func] - for sig in signatures: + for sig in supported_signatures: _match, return_type = self._match_single_function_signature( sig, func, *args, env=env ) @@ -129,14 +143,10 @@ def _match_function_signature(self, func, *args, env: TypeEnv): return return_type raise AnalogTypeError( - f"{func} signature must be one of:\n " + f"Got signature {self._print_function_signature(signature)} for {func}, " + "but signature must be one of:\n " + "\n ".join( - [ - f"({', '.join([get_type_name(x) for x in sig[0]])})" - " -> " - f"{get_type_name(sig[1])}" - for sig in signatures - ] + [self._print_function_signature(sig) for sig in supported_signatures] ) ) @@ -186,11 +196,6 @@ def _infer_function_signature(self, expr, *, env: TypeEnv): ) def _infer_type(self, expr, *, env: TypeEnv): - try: - print(env[expr.access.name]) - except: - pass - match expr: case Access(): return TAnalog if env is LatticeTop else env[expr.name] @@ -203,15 +208,22 @@ def _infer_type(self, expr, *, env: TypeEnv): case Bool(): return TBool case AnalogList(): - return TList[self._infer_type(expr.values[0], env=env)] - case QuantumRegister(): + elem_types = [self._infer_type(e, env=env) for e in expr.values] + + combined_elem_type = reduce( + self.lattice._element_lattice().join, elem_types + ) + + if self.lattice._element_lattice().leq(TAnalog, combined_elem_type): + raise AnalogTypeError( + f"List elements must all be compatible but got [{', '.join([get_type_name(e) for e in elem_types])}]" + ) + + return TList[combined_elem_type] + case QuantumRegister() | ModeRegister(): return TQReg - case ModeRegister(): - return TMReg case Extract() if env[expr.access.name] == TQReg: - return TQRef - case Extract() if env[expr.access.name] == TMReg: - return TMRef + return TQRegElem case Extract() if env[expr.access.name].__origin__ == TList: return env[expr.access.name].__args__[0] case ( diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index a6270244..e70d4217 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -69,22 +69,10 @@ class TBool(TAnalog): ... class TOp(TAnalog): ... -class TTarget(TAnalog): ... +class TQReg(TAnalog): ... -class TTargetRef(TTarget): ... - - -class TQReg(TTarget): ... - - -class TMReg(TTarget): ... - - -class TQRef(TTargetRef): ... - - -class TMRef(TTargetRef): ... +class TQRegElem(TQReg): ... class TNull(TAnalog): ... @@ -96,11 +84,9 @@ class TNull(TAnalog): ... def get_type_name(value: TLatticeValue): if issubclass(type(value), _GenericAlias): - return ( - f"{value.__name__}[{','.join(map(lambda x: x.__name__, value.__args__))}]" - ) + return f"{value.__name__[1:]}[{','.join(map(get_type_name, value.__args__))}]" - return value.__name__ + return value.__name__[1:] def isTList(value: TLatticeValue): @@ -187,19 +173,16 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ((TComplex, TComplex), TComplex), ], "Evolve": [ - ((TOp, TFloat, TTargetRef), TNull), - ((TOp, TFloat, TTarget), TNull), - ((TOp, TFloat, TList[TTargetRef]), TNull), + ((TOp, TFloat, TQReg), TNull), + ((TOp, TFloat, TList[TQRegElem]), TNull), ], "Initialize": [ - ((TTargetRef,), TNull), - ((TTarget,), TNull), - ((TList[TTargetRef],), TNull), + ((TQReg,), TNull), + ((TList[TQRegElem],), TNull), ], "Measure": [ - ((TTargetRef,), TList[TInt]), - ((TTarget,), TList[TInt]), - ((TList[TTargetRef],), TList[TInt]), + ((TQReg,), TList[TInt]), + ((TList[TQRegElem],), TList[TInt]), ], "abs": [((TInt,), TInt), ((TFloat,), TFloat), ((TComplex,), TFloat)], "sin": [((TFloat,), TFloat), ((TComplex,), TComplex)], diff --git a/uv.lock b/uv.lock index 9de31ed0..ff4f09e0 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#b551fb33709c73ef94f91a18635d4388fd9d5e66" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#1a5688a62e341a133425c3fee2ac38faf9dd2de2" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From bbf40b1b3fbb1c5a5f0ab56878bcd49e2ef26d81 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 10:07:30 -0400 Subject: [PATCH 06/25] [fix] use TLatticeTop for types and use bottom() instead of LatticeBottom, rename SUPPORTED_FUNC_SIGNATURES in analog to ANALOG_SUPPORTED_FUNC_SIGNATURES --- src/oqd_core/analysis/analog/type_checker.py | 9 +++++---- src/oqd_core/analysis/analog/types.py | 4 ++-- uv.lock | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 0d9e2f0a..dbc1b665 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -26,13 +26,12 @@ CFGBlock, DataflowResult, ForwardDataflowAnalysis, - LatticeBottom, LatticeTop, maplattice, ) from oqd_core.analysis.analog.types import ( - SUPPORTED_FUNC_SIGNATURES, + ANALOG_SUPPORTED_FUNC_SIGNATURES, AnalogTypeError, AnalogTypeLattice, TAnalog, @@ -105,7 +104,9 @@ def _match_single_function_signature(self, signature, func, *args, env: TypeEnv) if len(args) != len(sig_args_types): return False, None - if any([arg_type is LatticeBottom for arg_type in args]): + if any( + [arg_type is self.lattice._element_lattice().bottom() for arg_type in args] + ): return False, None if all( @@ -132,7 +133,7 @@ def _print_function_signature(self, signature): def _match_function_signature(self, func, *args, env: TypeEnv): signature = (args, None) - supported_signatures = SUPPORTED_FUNC_SIGNATURES[func] + supported_signatures = ANALOG_SUPPORTED_FUNC_SIGNATURES[func] for sig in supported_signatures: _match, return_type = self._match_single_function_signature( diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index e70d4217..869d6680 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -42,7 +42,7 @@ class TLatticeTop(LatticeTop): ... class TLatticeBottom(TLatticeTop): ... -class TAnalog(LatticeTop): ... +class TAnalog(TLatticeTop): ... LatticeValueTypeVar = TypeVar("LatticeValueTypeVar", bound=TLatticeTop) @@ -137,7 +137,7 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ######################################################################################## -SUPPORTED_FUNC_SIGNATURES = { +ANALOG_SUPPORTED_FUNC_SIGNATURES = { "BoolNot": [((TBool,), TBool)], "BoolEq": [((TScalar, TScalar), TBool)], "BoolNotEq": [((TScalar, TScalar), TBool)], diff --git a/uv.lock b/uv.lock index ff4f09e0..e85515a2 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#1a5688a62e341a133425c3fee2ac38faf9dd2de2" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#6e6407731c0159db2d42f73cbc4f64b0deacde01" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From 8ada3d16150be5780e54f8ee2e639aed7286ce63 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 10:10:18 -0400 Subject: [PATCH 07/25] [fix] type checker bug with empty list --- src/oqd_core/analysis/analog/type_checker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index dbc1b665..06a0567e 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -208,6 +208,8 @@ def _infer_type(self, expr, *, env: TypeEnv): return TInt if isinstance(expr.value, int) else TFloat case Bool(): return TBool + case AnalogList() if len(expr.values) == 0: + return TList[TAnalog] case AnalogList(): elem_types = [self._infer_type(e, env=env) for e in expr.values] From 9d4c5308da4287933180b7c91d3cc7f504e8b2d6 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 10:16:45 -0400 Subject: [PATCH 08/25] [refactor] move TLatticeBottom check to _match_function_signature and modified get_type_name --- src/oqd_core/analysis/analog/type_checker.py | 13 ++++++++----- src/oqd_core/analysis/analog/types.py | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 06a0567e..b36e42a1 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -104,11 +104,6 @@ def _match_single_function_signature(self, signature, func, *args, env: TypeEnv) if len(args) != len(sig_args_types): return False, None - if any( - [arg_type is self.lattice._element_lattice().bottom() for arg_type in args] - ): - return False, None - if all( [ self.lattice._element_lattice().leq(arg_type, sig_arg_type) @@ -135,6 +130,14 @@ def _match_function_signature(self, func, *args, env: TypeEnv): signature = (args, None) supported_signatures = ANALOG_SUPPORTED_FUNC_SIGNATURES[func] + if any( + [arg_type is self.lattice._element_lattice().bottom() for arg_type in args] + ): + raise AnalogTypeError( + f"Got signature {self._print_function_signature(signature)} containing TLatticeBottom for {func}, " + "these arguments type is inconsistent" + ) + for sig in supported_signatures: _match, return_type = self._match_single_function_signature( sig, func, *args, env=env diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index 869d6680..7377e4fb 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -84,9 +84,9 @@ class TNull(TAnalog): ... def get_type_name(value: TLatticeValue): if issubclass(type(value), _GenericAlias): - return f"{value.__name__[1:]}[{','.join(map(get_type_name, value.__args__))}]" + return f"{value.__name__}[{','.join(map(get_type_name, value.__args__))}]" - return value.__name__[1:] + return value.__name__ def isTList(value: TLatticeValue): From ba05d218bd1dc2dd20da99d61e47b6c49ed1f2f9 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 11:54:35 -0400 Subject: [PATCH 09/25] [fix] type system for type checker and mathfunc attribute access --- src/oqd_core/analysis/analog/type_checker.py | 4 +-- src/oqd_core/analysis/analog/types.py | 31 ++++++++++---------- uv.lock | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index b36e42a1..2eaf9019 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -106,7 +106,7 @@ def _match_single_function_signature(self, signature, func, *args, env: TypeEnv) if all( [ - self.lattice._element_lattice().leq(arg_type, sig_arg_type) + self.lattice._element_lattice().leq(sig_arg_type, arg_type) for arg_type, sig_arg_type in zip(args, sig_args_types) ] ): @@ -178,7 +178,7 @@ def _infer_function_signature(self, expr, *, env: TypeEnv): case MathFunc(): name = expr.func - args = expr.exprs if isinstance(expr.exprs, list) else [expr] + args = expr.expr if isinstance(expr.expr, list) else [expr.expr] case OperatorAdd() | OperatorKron() | OperatorMul(): name = expr.__class__.__name__ diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index 7377e4fb..828bb304 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -51,16 +51,13 @@ class TAnalog(TLatticeTop): ... class TList(TAnalog, Generic[LatticeValueTypeVar]): ... -class TScalar(TAnalog): ... +class TInt(TAnalog): ... -class TComplex(TScalar): ... +class TFloat(TInt): ... -class TFloat(TComplex): ... - - -class TInt(TFloat): ... +class TComplex(TFloat): ... class TBool(TAnalog): ... @@ -69,10 +66,10 @@ class TBool(TAnalog): ... class TOp(TAnalog): ... -class TQReg(TAnalog): ... +class TQRegElem(TAnalog): ... -class TQRegElem(TQReg): ... +class TQReg(TAnalog): ... class TNull(TAnalog): ... @@ -139,12 +136,12 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ANALOG_SUPPORTED_FUNC_SIGNATURES = { "BoolNot": [((TBool,), TBool)], - "BoolEq": [((TScalar, TScalar), TBool)], - "BoolNotEq": [((TScalar, TScalar), TBool)], - "BoolLessThan": [((TScalar, TScalar), TBool)], - "BoolLessThanEq": [((TScalar, TScalar), TBool)], - "BoolGreaterThan": [((TScalar, TScalar), TBool)], - "BoolGreaterThanEq": [((TScalar, TScalar), TBool)], + "BoolEq": [((TComplex, TComplex), TBool)], + "BoolNotEq": [((TComplex, TComplex), TBool)], + "BoolLessThan": [((TFloat, TFloat), TBool)], + "BoolLessThanEq": [((TFloat, TFloat), TBool)], + "BoolGreaterThan": [((TFloat, TFloat), TBool)], + "BoolGreaterThanEq": [((TFloat, TFloat), TBool)], "MathAdd": [ ((TInt, TInt), TInt), ((TFloat, TFloat), TFloat), @@ -159,8 +156,8 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ((TInt, TInt), TInt), ((TFloat, TFloat), TFloat), ((TComplex, TComplex), TComplex), - ((TScalar, TOp), TOp), - ((TOp, TScalar), TOp), + ((TComplex, TOp), TOp), + ((TOp, TComplex), TOp), ], "MathDiv": [ ((TInt, TInt), TFloat), @@ -178,10 +175,12 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ], "Initialize": [ ((TQReg,), TNull), + ((TQRegElem,), TNull), ((TList[TQRegElem],), TNull), ], "Measure": [ ((TQReg,), TList[TInt]), + ((TQRegElem,), TNull), ((TList[TQRegElem],), TList[TInt]), ], "abs": [((TInt,), TInt), ((TFloat,), TFloat), ((TComplex,), TFloat)], diff --git a/uv.lock b/uv.lock index e85515a2..10f79960 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#6e6407731c0159db2d42f73cbc4f64b0deacde01" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#6e4cf8aa7f23d24e3a16f707ea60a146075db5b4" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From 4c15ee54bc57cfacacb3e623a28fc15f2c9aeb88 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 19:08:17 -0400 Subject: [PATCH 10/25] [clean] cleaned up data analysis to use new DataAnalysis interface and default analyze method --- src/oqd_core/analysis/analog/type_checker.py | 50 ++++---------------- src/oqd_core/analysis/analog/types.py | 19 ++++---- src/oqd_core/analysis/dominator.py | 37 ++------------- uv.lock | 2 +- 4 files changed, 22 insertions(+), 86 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 2eaf9019..180c1e74 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -17,14 +17,12 @@ from __future__ import annotations -from collections import deque from functools import reduce from typing import Dict, List from oqd_compiler_infrastructure import ( CFG, CFGBlock, - DataflowResult, ForwardDataflowAnalysis, LatticeTop, maplattice, @@ -95,7 +93,8 @@ class AnalogTypeChecker(ForwardDataflowAnalysis[int, CFGBlock, TypeEnv]): lattice = maplattice(AnalogTypeLattice)() - def __init__(self, runtime_var_types={}): + def __init__(self, runtime_var_types={}, **kwargs): + super().__init__(**kwargs) self.runtime_var_types = runtime_var_types def _match_single_function_signature(self, signature, func, *args, env: TypeEnv): @@ -245,50 +244,21 @@ def _infer_type(self, expr, *, env: TypeEnv): case _: return self._infer_function_signature(expr, env=env) - def init_state(self, nodes: List[int]) -> Dict[int, TypeEnv]: - return {node: LatticeTop for node in nodes} - - def analyze(self, graph: CFG) -> DataflowResult[int, TypeEnv]: - nodes = list(graph.nodes()) - boundary = self.init_state(nodes) - result = self.init_state(nodes) - - worklist = deque(nodes) - iterations = 0 - - while worklist: - node = worklist.popleft() - iterations += 1 - - srcs = list(self.sources(graph, node)) - if srcs: - merged_input = self.merge_intersection(result[n] for n in srcs) - else: - merged_input = result[node] - - if not self.lattice.equal(boundary[node], merged_input): - boundary[node] = merged_input - - next_result = self.transfer(graph, node, merged_input) - if self.lattice.equal(result[node], next_result): - continue - - result[node] = next_result - for target in self.targets(graph, node): - if target not in worklist: - worklist.append(target) - - return self.result(boundary, result, iterations) + def merge(self, states): + return self.merge_intersection(states) def transfer(self, graph: CFG, node_id: int, state_in: TypeEnv) -> TypeEnv: block = graph[node_id] - state_out = {} if state_in == LatticeTop else state_in.copy() + state_out = {} if state_in == self.lattice.top() else state_in.copy() for stmt in block.stmts: if block.edge_labels: - if self._infer_type(stmt, env=state_out) is not TBool: - raise AnalogTypeError("branch condition must be bool") + cond_type = self._infer_type(stmt, env=state_out) + if cond_type is not TBool: + raise AnalogTypeError( + f"branch condition must be TBool got ({get_type_name(cond_type)})" + ) continue if isinstance(stmt, (Break, Continue)): diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index 828bb304..b3d87f85 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -36,16 +36,13 @@ class AnalogTypeError(TypeError): ######################################################################################## -class TLatticeTop(LatticeTop): ... +class TAnalog(LatticeTop): ... -class TLatticeBottom(TLatticeTop): ... +class TInvalid(TAnalog): ... -class TAnalog(TLatticeTop): ... - - -LatticeValueTypeVar = TypeVar("LatticeValueTypeVar", bound=TLatticeTop) +LatticeValueTypeVar = TypeVar("LatticeValueTypeVar", bound=TAnalog) class TList(TAnalog, Generic[LatticeValueTypeVar]): ... @@ -75,7 +72,7 @@ class TQReg(TAnalog): ... class TNull(TAnalog): ... -TLatticeValue = Union[all_subclasses(TLatticeTop)] +TLatticeValue = Union[all_subclasses(TAnalog)] TypeEnv = dict[str, TLatticeValue] @@ -96,13 +93,13 @@ class AnalogTypeLattice(LatticeBase[TLatticeValue]): """Type lattice for analog expressions.""" def top(self): - return TLatticeTop + return TAnalog def bottom(self): - return TLatticeBottom + return TInvalid def leq(self, t1: TLatticeValue, t2: TLatticeValue) -> bool: - if t1 is TLatticeBottom: + if t1 is self.bottom(): return True if isTList(t1) and isTList(t2): return self.leq(t1.__args__[0], t2.__args__[0]) @@ -180,7 +177,7 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ], "Measure": [ ((TQReg,), TList[TInt]), - ((TQRegElem,), TNull), + ((TQRegElem,), TList[TInt]), ((TList[TQRegElem],), TList[TInt]), ], "abs": [((TInt,), TInt), ((TFloat,), TFloat), ((TComplex,), TFloat)], diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index 626c5de0..ac757acd 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -16,8 +16,6 @@ from __future__ import annotations -from collections import deque - from oqd_compiler_infrastructure import ( CFGBlock, ForwardDataflowAnalysis, @@ -32,40 +30,11 @@ class DominatorTreeAnalysis(ForwardDataflowAnalysis[int, CFGBlock, PowersetValue]): lattice = PowersetLattice() - def init_state(self, nodes): + def initial_state(self, nodes): return {node: {0} if n == 0 else LatticeTop for n, node in enumerate(nodes)} - def analyze(self, graph): - nodes = list(graph.nodes()) - boundary = self.init_state(nodes) - result = self.init_state(nodes) - - worklist = deque(nodes) - iterations = 0 - - while worklist: - node = worklist.popleft() - iterations += 1 - - srcs = list(self.sources(graph, node)) - if srcs: - merged_input = self.merge_intersection(result[n] for n in srcs) - else: - merged_input = result[node] - - if not self.lattice.equal(boundary[node], merged_input): - boundary[node] = merged_input - - next_result = self.transfer(graph, node, merged_input) - if self.lattice.equal(result[node], next_result): - continue - - result[node] = next_result - for target in self.targets(graph, node): - if target not in worklist: - worklist.append(target) - - return self.result(boundary, result, iterations) + def merge(self, states): + return self.merge_intersection(states) def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: return self.lattice.join(state_in, {node_id}) diff --git a/uv.lock b/uv.lock index 10f79960..693256ee 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#6e4cf8aa7f23d24e3a16f707ea60a146075db5b4" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#bc1d5ce9c18baa4b1b582f7a38ed5aaadafa3d92" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From 38e6fd5cf4448d9573813199ad46b856e1475fec Mon Sep 17 00:00:00 2001 From: yhteoh Date: Thu, 17 Sep 2026 19:18:37 -0400 Subject: [PATCH 11/25] [fix] use lattice equal instead of python equal --- src/oqd_core/analysis/analog/type_checker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 180c1e74..dd66f8f3 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -227,7 +227,9 @@ def _infer_type(self, expr, *, env: TypeEnv): return TList[combined_elem_type] case QuantumRegister() | ModeRegister(): return TQReg - case Extract() if env[expr.access.name] == TQReg: + case Extract() if self.lattice._element_lattice().equal( + env[expr.access.name], TQReg + ): return TQRegElem case Extract() if env[expr.access.name].__origin__ == TList: return env[expr.access.name].__args__[0] @@ -255,7 +257,7 @@ def transfer(self, graph: CFG, node_id: int, state_in: TypeEnv) -> TypeEnv: for stmt in block.stmts: if block.edge_labels: cond_type = self._infer_type(stmt, env=state_out) - if cond_type is not TBool: + if not self.lattice._element_lattice().equal(cond_type, TBool): raise AnalogTypeError( f"branch condition must be TBool got ({get_type_name(cond_type)})" ) From c880a606d4e6f73b036979f0b4bcbe1f67a17ff7 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 07:18:54 -0400 Subject: [PATCH 12/25] [fix] types missing supported function signatures --- src/oqd_core/analysis/analog/type_checker.py | 1 - src/oqd_core/analysis/analog/types.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index dd66f8f3..1832f3c0 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -18,7 +18,6 @@ from __future__ import annotations from functools import reduce -from typing import Dict, List from oqd_compiler_infrastructure import ( CFG, diff --git a/src/oqd_core/analysis/analog/types.py b/src/oqd_core/analysis/analog/types.py index b3d87f85..f99e4568 100644 --- a/src/oqd_core/analysis/analog/types.py +++ b/src/oqd_core/analysis/analog/types.py @@ -166,8 +166,13 @@ def meet(self, t1: TLatticeValue, t2: TLatticeValue) -> TLatticeValue: ((TFloat, TFloat), TFloat), ((TComplex, TComplex), TComplex), ], + "OperatorAdd": [((TOp, TOp), TOp)], + "OperatorSub": [((TOp, TOp), TOp)], + "OperatorMul": [((TOp, TOp), TOp)], + "OperatorKron": [((TOp, TOp), TOp)], "Evolve": [ ((TOp, TFloat, TQReg), TNull), + ((TOp, TFloat, TQRegElem), TNull), ((TOp, TFloat, TList[TQRegElem]), TNull), ], "Initialize": [ From 0fff4d1419f54838cf35c91b4da5a18e96942edc Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 07:19:22 -0400 Subject: [PATCH 13/25] [feat] Added PostDominatorTreeAnalysis --- src/oqd_core/analysis/dominator.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index ac757acd..3ccdd17e 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -17,6 +17,7 @@ from __future__ import annotations from oqd_compiler_infrastructure import ( + BackwardDataflowAnalysis, CFGBlock, ForwardDataflowAnalysis, LatticeTop, @@ -38,3 +39,22 @@ def merge(self, states): def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: return self.lattice.join(state_in, {node_id}) + + +######################################################################################## + + +class PostDominatorTreeAnalysis(BackwardDataflowAnalysis[int, CFGBlock, PowersetValue]): + lattice = PowersetLattice() + + def initial_state(self, nodes): + return { + node: {node} if n == len(nodes) - 1 else LatticeTop + for n, node in enumerate(nodes) + } + + def merge(self, states): + return self.merge_intersection(states) + + def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: + return self.lattice.join(state_in, {node_id}) From 7c5524d94f4edeccee1029f864ab873ffe639f22 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 07:20:12 -0400 Subject: [PATCH 14/25] [feat] Implemented dimension checker for verifying program has consnistent dimensions for operators and targets --- src/oqd_core/analysis/analog/dim_checker.py | 226 ++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 src/oqd_core/analysis/analog/dim_checker.py diff --git a/src/oqd_core/analysis/analog/dim_checker.py b/src/oqd_core/analysis/analog/dim_checker.py new file mode 100644 index 00000000..f6aa087c --- /dev/null +++ b/src/oqd_core/analysis/analog/dim_checker.py @@ -0,0 +1,226 @@ +# Copyright 2024-2025 Open Quantum Design + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +######################################################################################## + + +from __future__ import annotations + +from typing import List, Union + +from oqd_compiler_infrastructure import ( + CFGBlock, + ForwardDataflowAnalysis, + LatticeBase, + LatticeTop, + maplattice, +) + +from oqd_core.interface.analog import ( + Access, + AnalogList, + Annihilation, + Break, + Continue, + Creation, + Declaration, + Evolve, + Extract, + Identity, + MathMul, + ModeRegister, + OperatorAdd, + OperatorKron, + OperatorMul, + OperatorSub, + PauliI, + PauliX, + PauliY, + PauliZ, + QuantumRegister, +) + +######################################################################################## + + +class DAny(LatticeTop): ... + + +class DInvalid(DAny): ... + + +DQuantum = List[Union[int, "DQuantum"]] + +DLatticeValue = Union[DAny, DInvalid, DQuantum] + + +class DimensionLattice(LatticeBase[DLatticeValue]): + """Quantum dimension lattice for analog layer.""" + + def top(self): + return DAny + + def bottom(self): + return DInvalid + + def leq(self, t1: DLatticeValue, t2: DLatticeValue) -> bool: + if self.join(t1, t2) == t1: + return True + return False + + def join(self, t1: DLatticeValue, t2: DLatticeValue) -> DLatticeValue: + if t1 == t2: + return t1 + + if t1 == self.bottom(): + return t2 + + if t2 == self.bottom(): + return t1 + + return self.top() + + def meet(self, t1: DLatticeValue, t2: DLatticeValue) -> DLatticeValue: + if t1 == t2: + return t1 + + if t1 == self.top(): + return t2 + + if t2 == self.top(): + return t1 + + return self.bottom() + + +######################################################################################## + + +class DimensionError(Exception): ... + + +######################################################################################## + + +class DimensionChecker(ForwardDataflowAnalysis[int, CFGBlock, DLatticeValue]): + lattice = maplattice(DimensionLattice)() + + def merge(self, states): + return self.merge_intersection(states) + + def _infer_dim(self, expr, *, env): + match expr: + case Access(): + return env[expr.name] + + case PauliX() | PauliY() | PauliZ() | PauliI(): + return [2] + + case Annihilation() | Creation() | Identity(): + return [-1] + + case QuantumRegister(): + return [[2]] * expr.size + + case ModeRegister(): + return [[-1]] * expr.size + + case AnalogList(): + return [self._infer_dim(element, env=env) for element in expr.values] + + case Extract(): + value = self._infer_dim(expr.access, env=env) + + return value if value == DInvalid else value[expr.index] + + case OperatorAdd() | OperatorSub(): + args = ( + self._infer_dim(expr.op1, env=env), + self._infer_dim(expr.op2, env=env), + ) + + if not self.lattice._element_lattice().equal(args[0], args[1]): + raise DimensionError() + + return args[0] + + case OperatorKron(): + args = ( + self._infer_dim(expr.op1, env=env), + self._infer_dim(expr.op2, env=env), + ) + + return args[0] + args[1] + + case OperatorMul(): + args = ( + self._infer_dim(expr.op1, env=env), + self._infer_dim(expr.op2, env=env), + ) + + if not self.lattice._element_lattice().equal(args[0], args[1]): + raise DimensionError() + + return args[0] + + case MathMul(): + args = ( + self._infer_dim(expr.expr1, env=env), + self._infer_dim(expr.expr2, env=env), + ) + + return self.lattice._element_lattice().join(args) + + case Evolve(): + args = ( + self._infer_dim(expr.hamiltonian, env=env), + self._infer_dim(expr.targets, env=env), + ) + + if self.lattice._element_lattice().equal(args[0], args[1]): + return DInvalid + + if all( + map(lambda x: isinstance(x, list), args[1]) + ) and self.lattice._element_lattice().equal( + args[0], [a[0] for a in args[1]] + ): + return DInvalid + + raise DimensionError( + f"Got Hamiltonian dimensions ({args[0]}) and target dimensions ({args[1]}), expected target dimensions to be one of:\n" + f" {args[0]}\n" + f" {[[a] for a in args[0]]}" + ) + + case _: + return DInvalid + + def transfer(self, graph, node_id, state_in): + block = graph[node_id] + + state_out = {} if state_in == self.lattice.top() else state_in.copy() + + for stmt in block.stmts: + match stmt: + case _ if block.edge_labels: + continue + case Continue() | Break(): + continue + case Declaration(): + state_out[stmt.name] = self._infer_dim(stmt.value, env=state_out) + case _: + self._infer_dim(stmt, env=state_out) + + return state_out From a5e4e4959976df79aa9097cfebee765421560f99 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 08:15:11 -0400 Subject: [PATCH 15/25] [clean] imports of dominator module --- src/oqd_core/analysis/dominator.py | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index 3ccdd17e..e079ae4a 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -22,8 +22,8 @@ ForwardDataflowAnalysis, LatticeTop, PowersetLattice, + PowersetValue, ) -from oqd_compiler_infrastructure.lattice import PowersetValue ######################################################################################## diff --git a/uv.lock b/uv.lock index 693256ee..e67052f9 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#bc1d5ce9c18baa4b1b582f7a38ed5aaadafa3d92" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#2c6d49898d0869f5d2b933c9cd61e7e70762daa3" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From cf3090794e10e0b54368760501fc7da79a23ff79 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 09:57:38 -0400 Subject: [PATCH 16/25] [fix] dominator lattice values --- src/oqd_core/analysis/dominator.py | 24 +++++++++++++++++------- uv.lock | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index e079ae4a..13df7f47 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -22,14 +22,18 @@ ForwardDataflowAnalysis, LatticeTop, PowersetLattice, - PowersetValue, + PowersetLatticeValue, ) ######################################################################################## +DominatorLatticeValue = PowersetLatticeValue[int] -class DominatorTreeAnalysis(ForwardDataflowAnalysis[int, CFGBlock, PowersetValue]): - lattice = PowersetLattice() + +class DominatorTreeAnalysis( + ForwardDataflowAnalysis[int, CFGBlock, DominatorLatticeValue] +): + lattice = PowersetLattice[DominatorLatticeValue]() def initial_state(self, nodes): return {node: {0} if n == 0 else LatticeTop for n, node in enumerate(nodes)} @@ -37,15 +41,19 @@ def initial_state(self, nodes): def merge(self, states): return self.merge_intersection(states) - def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: + def transfer( + self, graph, node_id: int, state_in: DominatorLatticeValue + ) -> DominatorLatticeValue: return self.lattice.join(state_in, {node_id}) ######################################################################################## -class PostDominatorTreeAnalysis(BackwardDataflowAnalysis[int, CFGBlock, PowersetValue]): - lattice = PowersetLattice() +class PostDominatorTreeAnalysis( + BackwardDataflowAnalysis[int, CFGBlock, DominatorLatticeValue] +): + lattice = PowersetLattice[DominatorLatticeValue]() def initial_state(self, nodes): return { @@ -56,5 +64,7 @@ def initial_state(self, nodes): def merge(self, states): return self.merge_intersection(states) - def transfer(self, graph, node_id: int, state_in: PowersetValue) -> PowersetValue: + def transfer( + self, graph, node_id: int, state_in: DominatorLatticeValue + ) -> DominatorLatticeValue: return self.lattice.join(state_in, {node_id}) diff --git a/uv.lock b/uv.lock index e67052f9..ea07c7d1 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#2c6d49898d0869f5d2b933c9cd61e7e70762daa3" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#1d1282e660d235d79d770805da7508e27367bf04" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From a7a8a03e557382a3df1c3c4fc4728175cfe872b4 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Fri, 18 Sep 2026 18:28:52 -0400 Subject: [PATCH 17/25] [fix] Use updated access into element lattice for maplattices --- src/oqd_core/analysis/analog/dim_checker.py | 10 +++++----- src/oqd_core/analysis/analog/type_checker.py | 12 ++++++------ uv.lock | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/oqd_core/analysis/analog/dim_checker.py b/src/oqd_core/analysis/analog/dim_checker.py index f6aa087c..2b564a0b 100644 --- a/src/oqd_core/analysis/analog/dim_checker.py +++ b/src/oqd_core/analysis/analog/dim_checker.py @@ -150,7 +150,7 @@ def _infer_dim(self, expr, *, env): self._infer_dim(expr.op2, env=env), ) - if not self.lattice._element_lattice().equal(args[0], args[1]): + if not self.lattice.element_lattice.equal(args[0], args[1]): raise DimensionError() return args[0] @@ -169,7 +169,7 @@ def _infer_dim(self, expr, *, env): self._infer_dim(expr.op2, env=env), ) - if not self.lattice._element_lattice().equal(args[0], args[1]): + if not self.lattice.element_lattice.equal(args[0], args[1]): raise DimensionError() return args[0] @@ -180,7 +180,7 @@ def _infer_dim(self, expr, *, env): self._infer_dim(expr.expr2, env=env), ) - return self.lattice._element_lattice().join(args) + return self.lattice.element_lattice.join(args) case Evolve(): args = ( @@ -188,12 +188,12 @@ def _infer_dim(self, expr, *, env): self._infer_dim(expr.targets, env=env), ) - if self.lattice._element_lattice().equal(args[0], args[1]): + if self.lattice.element_lattice.equal(args[0], args[1]): return DInvalid if all( map(lambda x: isinstance(x, list), args[1]) - ) and self.lattice._element_lattice().equal( + ) and self.lattice.element_lattice.equal( args[0], [a[0] for a in args[1]] ): return DInvalid diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index 1832f3c0..b454aac0 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -104,7 +104,7 @@ def _match_single_function_signature(self, signature, func, *args, env: TypeEnv) if all( [ - self.lattice._element_lattice().leq(sig_arg_type, arg_type) + self.lattice.element_lattice.leq(sig_arg_type, arg_type) for arg_type, sig_arg_type in zip(args, sig_args_types) ] ): @@ -129,7 +129,7 @@ def _match_function_signature(self, func, *args, env: TypeEnv): supported_signatures = ANALOG_SUPPORTED_FUNC_SIGNATURES[func] if any( - [arg_type is self.lattice._element_lattice().bottom() for arg_type in args] + [arg_type is self.lattice.element_lattice.bottom() for arg_type in args] ): raise AnalogTypeError( f"Got signature {self._print_function_signature(signature)} containing TLatticeBottom for {func}, " @@ -215,10 +215,10 @@ def _infer_type(self, expr, *, env: TypeEnv): elem_types = [self._infer_type(e, env=env) for e in expr.values] combined_elem_type = reduce( - self.lattice._element_lattice().join, elem_types + self.lattice.element_lattice.join, elem_types ) - if self.lattice._element_lattice().leq(TAnalog, combined_elem_type): + if self.lattice.element_lattice.leq(TAnalog, combined_elem_type): raise AnalogTypeError( f"List elements must all be compatible but got [{', '.join([get_type_name(e) for e in elem_types])}]" ) @@ -226,7 +226,7 @@ def _infer_type(self, expr, *, env: TypeEnv): return TList[combined_elem_type] case QuantumRegister() | ModeRegister(): return TQReg - case Extract() if self.lattice._element_lattice().equal( + case Extract() if self.lattice.element_lattice.equal( env[expr.access.name], TQReg ): return TQRegElem @@ -256,7 +256,7 @@ def transfer(self, graph: CFG, node_id: int, state_in: TypeEnv) -> TypeEnv: for stmt in block.stmts: if block.edge_labels: cond_type = self._infer_type(stmt, env=state_out) - if not self.lattice._element_lattice().equal(cond_type, TBool): + if not self.lattice.element_lattice.equal(cond_type, TBool): raise AnalogTypeError( f"branch condition must be TBool got ({get_type_name(cond_type)})" ) diff --git a/uv.lock b/uv.lock index ea07c7d1..e82c9c54 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#1d1282e660d235d79d770805da7508e27367bf04" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#c8c51f4822e7632ae128322113377bcfd547e12a" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From 794b10341265701ae7b19647c0e226400dccf8f5 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 13:20:45 -0400 Subject: [PATCH 18/25] [feat] Implented tags for CFG and conversion from CFG back to AST --- src/oqd_core/analysis/analog/cfg.py | 115 +++++++++++++++++++++++++++- uv.lock | 2 +- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/oqd_core/analysis/analog/cfg.py b/src/oqd_core/analysis/analog/cfg.py index ab08e773..93f8aa96 100644 --- a/src/oqd_core/analysis/analog/cfg.py +++ b/src/oqd_core/analysis/analog/cfg.py @@ -15,6 +15,8 @@ from __future__ import annotations +from functools import reduce + from oqd_compiler_infrastructure import CFG, CFGBlock, RewriteRule from oqd_core.interface.analog import ( @@ -27,9 +29,12 @@ class AnalogCFGBuilder(RewriteRule): - def new_node(self, preds, stmt): + def new_node(self, preds, stmt, tags=None): node = CFGBlock( - register_id=self.index, stmts=[stmt] if stmt else [], preds=preds + register_id=self.index, + stmts=[stmt] if stmt else [], + preds=preds, + tags=tags if tags else {}, ) self.blocks[node.register_id] = node self.index += 1 @@ -79,7 +84,11 @@ def map_AnalogCircuit(self, model: AnalogCircuit) -> CFG: return CFG(blocks=self.blocks) def map_IfElse(self, model: IfElse): - node = self.new_node(self.preds, model.condition) + node = self.new_node( + self.preds, + model.condition, + tags={"__scf__": model.__class__.__qualname__}, + ) then_branch = self.walk_block(model.then_branch, [node], entry_label="true") if model.else_branch: else_branch = self.walk_block( @@ -91,7 +100,11 @@ def map_IfElse(self, model: IfElse): return then_branch + [node] def map_While(self, model: While): - node = self.new_node(self.preds, model.condition) + node = self.new_node( + self.preds, + model.condition, + tags={"__scf__": model.__class__.__qualname__}, + ) self.fallthrough_labels[node] = "false" self.loop_stack.append(node) body = self.walk_block(model.body, [node], entry_label="true") @@ -121,3 +134,97 @@ def map_Continue(self, model: Continue): def generic_map(self, model): return [self.new_node(self.preds, model)] + + +class AnalogCFGtoAST(RewriteRule): + def __init__(self, pdom_result): + super().__init__() + self._pdom_result = pdom_result + + @property + def pdom(self): + return self._pdom_result.out_states + + @property + def dataflow_analysis(self): + return self._pdom_result.dataflow_analysis + + @property + def lattice(self): + return self._pdom_result.dataflow_analysis.lattice + + def _consume(self, blocks, start=0, until=None): + succ = start + statements = [] + while succ in blocks.keys(): + if until and succ in until: + break + + current_block = blocks.pop(succ) + + match current_block.tags.get("__scf__", None): + case "IfElse": + ifelse_until = reduce( + self.lattice.meet, + [self.pdom[succ] for succ in current_block.succs], + ) + + reversed_edge_labels = { + v: k for k, v in current_block.edge_labels.items() + } + + then_block, then_succ = self._consume( + blocks, start=reversed_edge_labels["true"], until=ifelse_until + ) + + else_block, else_succ = self._consume( + blocks, start=reversed_edge_labels["false"], until=ifelse_until + ) + + statements.append( + IfElse( + condition=current_block.stmts[0], + then_branch=then_block, + else_branch=else_block, + ) + ) + + succ = else_succ + + case "While": + while_until = reduce( + self.lattice.meet, + [self.pdom[succ] for succ in current_block.succs], + ) + + reversed_edge_labels = { + v: k for k, v in current_block.edge_labels.items() + } + + loop_block, loop_succ = self._consume( + blocks, start=reversed_edge_labels["true"], until=while_until + ) + + statements.append( + While(condition=current_block.stmts[0], body=loop_block) + ) + + succ = reversed_edge_labels["false"] + + case _: + statements.extend(current_block.stmts) + + if not current_block.succs: + break + + succ = current_block.succs[0] + + return statements, succ + + def map_CFG(self, model): + circuit = AnalogCircuit() + + statements, _ = self._consume(model.blocks) + circuit.statements.extend(statements) + + return circuit diff --git a/uv.lock b/uv.lock index e82c9c54..65b34843 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#c8c51f4822e7632ae128322113377bcfd547e12a" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#3eb7204c85ed41a0b8901821b78a98d3fbd2b10c" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From caae68a9795268db688b0eed3734710bdf885e1c Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 16:13:16 -0400 Subject: [PATCH 19/25] [fix] bug with empty blocks in while and ifelse statements, use changed edge labels for CFG --- examples/analog/test.analog | 6 +++ src/oqd_core/analysis/analog/cfg.py | 79 ++++++++++++++++++----------- uv.lock | 2 +- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/examples/analog/test.analog b/examples/analog/test.analog index 2cce6c34..0ef917ac 100644 --- a/examples/analog/test.analog +++ b/examples/analog/test.analog @@ -91,3 +91,9 @@ a = true if (a) { b = 5 } + + +if (a) {} + + +while (a) {} \ No newline at end of file diff --git a/src/oqd_core/analysis/analog/cfg.py b/src/oqd_core/analysis/analog/cfg.py index 93f8aa96..fb53a1e5 100644 --- a/src/oqd_core/analysis/analog/cfg.py +++ b/src/oqd_core/analysis/analog/cfg.py @@ -15,6 +15,7 @@ from __future__ import annotations +from collections.abc import Iterable from functools import reduce from oqd_compiler_infrastructure import CFG, CFGBlock, RewriteRule @@ -46,6 +47,7 @@ def new_node(self, preds, stmt, tags=None): label = explicit_labels.get(pred) if label is None: label = self.fallthrough_labels.pop(pred, None) + self.blocks[pred].add_succ(node.register_id, label=label) return node.register_id @@ -78,9 +80,9 @@ def map_AnalogCircuit(self, model: AnalogCircuit) -> CFG: self.preds = [] self.edge_labels = None self.fallthrough_labels = {} - node = self.new_node([], {}) + node = self.new_node([], []) node = self.walk_block(model.statements, [node]) - node = self.new_node(node, {}) + node = self.new_node(node, []) return CFG(blocks=self.blocks) def map_IfElse(self, model: IfElse): @@ -89,15 +91,27 @@ def map_IfElse(self, model: IfElse): model.condition, tags={"__scf__": model.__class__.__qualname__}, ) - then_branch = self.walk_block(model.then_branch, [node], entry_label="true") - if model.else_branch: - else_branch = self.walk_block( - model.else_branch, [node], entry_label="false" - ) - return then_branch + else_branch - self.fallthrough_labels[node] = "false" - return then_branch + [node] + then_branch = ( + self.walk_block(model.then_branch, [node], entry_label="true") + if model.then_branch + else [node] + ) + else_branch = ( + self.walk_block(model.else_branch, [node], entry_label="false") + if model.else_branch + else [node] + ) + + fallthrough_labels = [] + if model.then_branch == []: + fallthrough_labels.append("true") + if model.else_branch == []: + fallthrough_labels.append("false") + + self.fallthrough_labels[node] = fallthrough_labels + + return then_branch + else_branch def map_While(self, model: While): node = self.new_node( @@ -105,15 +119,20 @@ def map_While(self, model: While): model.condition, tags={"__scf__": model.__class__.__qualname__}, ) - self.fallthrough_labels[node] = "false" - self.loop_stack.append(node) - body = self.walk_block(model.body, [node], entry_label="true") - self.loop_stack.pop() - self.blocks[node].add_preds(body) - for s in body: - label = self.fallthrough_labels.pop(s, None) - self.blocks[s].add_succ(node, label=label) + if model.body: + self.loop_stack.append(node) + body = self.walk_block(model.body, [node], entry_label="true") + self.loop_stack.pop() + + self.blocks[node].add_preds(body) + for loop_back in body: + label = self.fallthrough_labels.pop(loop_back, None) + self.blocks[loop_back].add_succ(node, label=label) + else: + self.blocks[node].add_succ(node, label="true") + + self.fallthrough_labels[node] = "false" return self.blocks[node].exit_nodes + [node] @@ -169,16 +188,16 @@ def _consume(self, blocks, start=0, until=None): [self.pdom[succ] for succ in current_block.succs], ) - reversed_edge_labels = { - v: k for k, v in current_block.edge_labels.items() - } - then_block, then_succ = self._consume( - blocks, start=reversed_edge_labels["true"], until=ifelse_until + blocks, + start=current_block.edge_labels["true"], + until=ifelse_until, ) else_block, else_succ = self._consume( - blocks, start=reversed_edge_labels["false"], until=ifelse_until + blocks, + start=current_block.edge_labels["false"], + until=ifelse_until, ) statements.append( @@ -197,19 +216,17 @@ def _consume(self, blocks, start=0, until=None): [self.pdom[succ] for succ in current_block.succs], ) - reversed_edge_labels = { - v: k for k, v in current_block.edge_labels.items() - } - loop_block, loop_succ = self._consume( - blocks, start=reversed_edge_labels["true"], until=while_until + blocks, + start=current_block.edge_labels["true"], + until=while_until, ) statements.append( While(condition=current_block.stmts[0], body=loop_block) ) - succ = reversed_edge_labels["false"] + succ = current_block.edge_labels["false"] case _: statements.extend(current_block.stmts) @@ -217,7 +234,7 @@ def _consume(self, blocks, start=0, until=None): if not current_block.succs: break - succ = current_block.succs[0] + succ = list(current_block.succs)[0] return statements, succ diff --git a/uv.lock b/uv.lock index 65b34843..3ba9821c 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#3eb7204c85ed41a0b8901821b78a98d3fbd2b10c" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#ef005fb92c23597d085cd57cc3bb09a1f3db0be3" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From bb49f475d06ca86b25a37386421c1b3e61a3eb48 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 18:12:40 -0400 Subject: [PATCH 20/25] [fix] updated merge functions of data analysis, merge functions now coming from lattice instead of DataflowAnalysis base class --- src/oqd_core/analysis/analog/dim_checker.py | 2 +- src/oqd_core/analysis/analog/type_checker.py | 2 +- src/oqd_core/analysis/dominator.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oqd_core/analysis/analog/dim_checker.py b/src/oqd_core/analysis/analog/dim_checker.py index 2b564a0b..5f369561 100644 --- a/src/oqd_core/analysis/analog/dim_checker.py +++ b/src/oqd_core/analysis/analog/dim_checker.py @@ -117,7 +117,7 @@ class DimensionChecker(ForwardDataflowAnalysis[int, CFGBlock, DLatticeValue]): lattice = maplattice(DimensionLattice)() def merge(self, states): - return self.merge_intersection(states) + return self.lattice.merge_meet(states) def _infer_dim(self, expr, *, env): match expr: diff --git a/src/oqd_core/analysis/analog/type_checker.py b/src/oqd_core/analysis/analog/type_checker.py index b454aac0..0556ec66 100644 --- a/src/oqd_core/analysis/analog/type_checker.py +++ b/src/oqd_core/analysis/analog/type_checker.py @@ -246,7 +246,7 @@ def _infer_type(self, expr, *, env: TypeEnv): return self._infer_function_signature(expr, env=env) def merge(self, states): - return self.merge_intersection(states) + return self.lattice.merge_meet(states) def transfer(self, graph: CFG, node_id: int, state_in: TypeEnv) -> TypeEnv: block = graph[node_id] diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index 13df7f47..89009d11 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -39,7 +39,7 @@ def initial_state(self, nodes): return {node: {0} if n == 0 else LatticeTop for n, node in enumerate(nodes)} def merge(self, states): - return self.merge_intersection(states) + return self.lattice.merge_meet(states) def transfer( self, graph, node_id: int, state_in: DominatorLatticeValue @@ -62,7 +62,7 @@ def initial_state(self, nodes): } def merge(self, states): - return self.merge_intersection(states) + return self.lattice.merge_meet(states) def transfer( self, graph, node_id: int, state_in: DominatorLatticeValue From 3c11c1d9a86282024d9f41c856999b31424fadf3 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 19:57:31 -0400 Subject: [PATCH 21/25] [feat] Add indentation for while and ifelse in serialize_analog --- src/oqd_core/frontend/analog/serialize.py | 38 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/oqd_core/frontend/analog/serialize.py b/src/oqd_core/frontend/analog/serialize.py index a333c24d..7db83404 100644 --- a/src/oqd_core/frontend/analog/serialize.py +++ b/src/oqd_core/frontend/analog/serialize.py @@ -71,13 +71,29 @@ class SerializeAnalog(ConversionRule): + def __init__(self, indent=2): + super().__init__() + self.indent = indent + def generic_map(self, model, operands): if model is None or isinstance(model, (str, int, float, bool)): return model raise TypeError(f"Unsupported node: {model}") + def _indent_block(self, body): + body_str = "\n".join(body) + body_str = "\n".join( + map(lambda x: " " * self.indent + x, body_str.splitlines()) + ) + + if body_str: + body_str = "\n" + body_str + "\n" + + return body_str + def map_AnalogCircuit(self, model: AnalogCircuit, operands): statements = operands["statements"] + return "\n".join(statements) + "\n" ## Statements ## @@ -86,16 +102,16 @@ def map_Declaration(self, model: Declaration, operands): return f"{operands['name']} = {operands['value']}" def map_While(self, model: While, operands): - body = "\n".join(operands["body"]) + "\n" - return f"while ({operands['condition']}) {{\n{body}}}" + body_str = self._indent_block(operands["body"]) + return f"while ({operands['condition']}) {{{body_str}}}" def map_IfElse(self, model: IfElse, operands): - then_branch = "\n".join(operands["then_branch"]) - else_branch = operands["else_branch"] - if else_branch: - else_branch = "\n".join(else_branch) - return f"if ({operands['condition']}) {{\n{then_branch}\n}} else {{\n{else_branch}\n}}" - return f"if ({operands['condition']}) {{\n{then_branch}\n}}" + then_str = self._indent_block(operands["then_branch"]) + else_str = self._indent_block(operands["else_branch"]) + + return f"if ({operands['condition']}) {{{then_str}}}\n" + ( + f"else {{{else_str}}}" if else_str else "" + ) def map_Break(self, model: Break, operands): return "break" @@ -240,6 +256,12 @@ def map_BoolNotEq(self, model: BoolNotEq, operands): def map_BoolLessThan(self, model: BoolLessThan, operands): return f"{operands['expr1']} < {operands['expr2']}" + # then_branch = "\n".join(operands["then_branch"]) + # else_branch = operands["else_branch"] + # if else_branch: + # else_branch = "\n".join(else_branch) + # return f"if ({operands['condition']}) {{\n{then_branch}\n}} else {{\n{else_branch}\n}}" + def map_BoolLessThanEq(self, model: BoolLessThanEq, operands): return f"{operands['expr1']} <= {operands['expr2']}" From 033cc13cdea964f593ea6109384865767a16f6cb Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 19:58:31 -0400 Subject: [PATCH 22/25] [refactor, fix] fixed AnalogCFGtoAST missing dead blocks and cleaned up code. --- src/oqd_core/analysis/analog/cfg.py | 112 +++++++++++++++++----------- 1 file changed, 68 insertions(+), 44 deletions(-) diff --git a/src/oqd_core/analysis/analog/cfg.py b/src/oqd_core/analysis/analog/cfg.py index fb53a1e5..6910a200 100644 --- a/src/oqd_core/analysis/analog/cfg.py +++ b/src/oqd_core/analysis/analog/cfg.py @@ -172,6 +172,62 @@ def dataflow_analysis(self): def lattice(self): return self._pdom_result.dataflow_analysis.lattice + def _consume_ifelse(self, current_block, blocks): + ifelse_until = reduce( + self.lattice.meet, + [self.pdom[succ] for succ in current_block.succs], + ) + + then_block, then_succ = self._consume( + blocks, + start=current_block.edge_labels["true"], + until=ifelse_until, + ) + + else_block, else_succ = self._consume( + blocks, + start=current_block.edge_labels["false"], + until=ifelse_until, + ) + + return IfElse( + condition=current_block.stmts[0], + then_branch=then_block, + else_branch=else_block, + ), else_succ + + def _get_while_dead_blocks(self, blocks, end): + return [ + b + for b in blocks.keys() + if (len(blocks[b].preds) == 0 and end in self.pdom[b]) + ] + + def _consume_while(self, current_block, blocks): + while_until = reduce( + self.lattice.meet, + [self.pdom[succ] for succ in current_block.succs], + ) + + loop_block, loop_succ = self._consume( + blocks, + start=current_block.edge_labels["true"], + until=while_until, + ) + + while_dead_blocks = self._get_while_dead_blocks( + blocks, current_block.register_id + ) + + for b in sorted(while_dead_blocks): + loop_block.extend( + self._consume(blocks, start=b, until={current_block.register_id})[0] + ) + + return While( + condition=current_block.stmts[0], body=loop_block + ), current_block.edge_labels["false"] + def _consume(self, blocks, start=0, until=None): succ = start statements = [] @@ -181,59 +237,27 @@ def _consume(self, blocks, start=0, until=None): current_block = blocks.pop(succ) + if len(current_block.succs) == 0: + statements.extend(current_block.stmts) + break + match current_block.tags.get("__scf__", None): case "IfElse": - ifelse_until = reduce( - self.lattice.meet, - [self.pdom[succ] for succ in current_block.succs], - ) - - then_block, then_succ = self._consume( - blocks, - start=current_block.edge_labels["true"], - until=ifelse_until, - ) - - else_block, else_succ = self._consume( - blocks, - start=current_block.edge_labels["false"], - until=ifelse_until, - ) - - statements.append( - IfElse( - condition=current_block.stmts[0], - then_branch=then_block, - else_branch=else_block, - ) + ifelse_statement, ifelse_succ = self._consume_ifelse( + current_block, blocks ) - - succ = else_succ + statements.append(ifelse_statement) + succ = ifelse_succ case "While": - while_until = reduce( - self.lattice.meet, - [self.pdom[succ] for succ in current_block.succs], + while_statement, while_succ = self._consume_while( + current_block, blocks ) - - loop_block, loop_succ = self._consume( - blocks, - start=current_block.edge_labels["true"], - until=while_until, - ) - - statements.append( - While(condition=current_block.stmts[0], body=loop_block) - ) - - succ = current_block.edge_labels["false"] + statements.append(while_statement) + succ = while_succ case _: statements.extend(current_block.stmts) - - if not current_block.succs: - break - succ = list(current_block.succs)[0] return statements, succ From 83185e5e9875475c2847c1033c2984cd0cbc9b7a Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 20:03:28 -0400 Subject: [PATCH 23/25] [fix] AnalogCFGtoAST while dead blocks should blocks without preds that has the false branch of the while loop in its postdominance --- src/oqd_core/analysis/analog/cfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oqd_core/analysis/analog/cfg.py b/src/oqd_core/analysis/analog/cfg.py index 6910a200..34a66749 100644 --- a/src/oqd_core/analysis/analog/cfg.py +++ b/src/oqd_core/analysis/analog/cfg.py @@ -216,7 +216,7 @@ def _consume_while(self, current_block, blocks): ) while_dead_blocks = self._get_while_dead_blocks( - blocks, current_block.register_id + blocks, current_block.edge_labels["false"] ) for b in sorted(while_dead_blocks): From fa828989c6d8cb53308aef44a89754d949f26522 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sat, 19 Sep 2026 23:08:23 -0400 Subject: [PATCH 24/25] [feat] implemented dominator tree and dominance frontier --- src/oqd_core/analysis/dominator.py | 163 ++++++++++++++++++++++++++++- uv.lock | 2 +- 2 files changed, 159 insertions(+), 6 deletions(-) diff --git a/src/oqd_core/analysis/dominator.py b/src/oqd_core/analysis/dominator.py index 89009d11..c78f66c9 100644 --- a/src/oqd_core/analysis/dominator.py +++ b/src/oqd_core/analysis/dominator.py @@ -16,23 +16,156 @@ from __future__ import annotations +from collections.abc import MutableMapping +from typing import Dict, Set, Tuple, Union + +import graphviz from oqd_compiler_infrastructure import ( + CFG, BackwardDataflowAnalysis, CFGBlock, + DataflowResult, ForwardDataflowAnalysis, + GraphProtocol, LatticeTop, PowersetLattice, PowersetLatticeValue, ) +from pydantic import BaseModel, computed_field ######################################################################################## + +DominatorTreeNode = Tuple[int, Set[int], Set[int]] + + +class DominatorTree(BaseModel, GraphProtocol, MutableMapping[int, DominatorTreeNode]): + dominator_tree: Dict[int, Tuple[int, Set[int], Set[int]]] + + def __len__(self): + return len(self.dominator_tree) + + def __getitem__(self, idx): + return self.dominator_tree[idx] + + def __setitem__(self, idx, value): + self.dominator_tree[idx] = value + + def __delitem__(self, idx): + del self.dominator_tree[idx] + + def __iter__(self): + return iter(self.dominator_tree) + + def nodes(self): + return self.keys() + + def predecessors(self, n): + return self.dominator_tree[n][1] + + def successors(self, n): + return self.dominator_tree[n][2] + + def to_dot(self): + G = graphviz.Digraph() + for node in self.nodes(): + G.node(str(node)) + for pred in self.predecessors(node): + G.edge(str(pred), str(node)) + + return G + + +class DominatorDataflowResult(DataflowResult): + dataflow_analysis: Union[DominatorAnalysis, PostDominatorAnalysis] + graph: CFG + in_states: Dict[int, DominatorLatticeValue] + out_states: Dict[int, DominatorLatticeValue] + iterations: int + post: bool + + @computed_field + @property + def dominators(self) -> Dict[int, Set[int]]: + return {k: set() if v is LatticeTop else v for k, v in self.out_states.items()} + + def dominates(self, d, n): + return d in self.dominators[n] + + @computed_field + @property + def strict_dominators(self) -> Dict[int, Set[int]]: + return {k: v - {k} for k, v in self.dominators.items()} + + def strictly_dominates(self, d, n): + return d in self.strict_dominators[n] + + @computed_field + @property + def immediate_dominators(self) -> Dict[int, Set[int]]: + return { + k: set( + filter( + lambda s: all([s not in self.strict_dominators[n] for n in v]), + v, + ) + ) + for k, v in self.strict_dominators.items() + } + + def immediately_dominates(self, d, n): + return d in self.immediate_dominators[n] + + @computed_field + @property + def dominator_tree(self) -> DominatorTree: + tree = {} + + nodes = sorted(self.out_states.keys(), reverse=self.post) + + while nodes: + n = nodes.pop(0) + + try: + idom = self.immediate_dominators[n] + + if idom: + idom = next(iter(idom)) + tree[n] = (n, {idom}, set()) + tree[idom] = (*tree[idom][:2], {n}) + else: + tree[n] = (n, set(), set()) + except KeyError: + nodes.append(n) + + return DominatorTree(dominator_tree=tree) + + @computed_field + @property + def dominance_frontier(self) -> Dict[int, Set[int]]: + return { + d: set( + filter( + lambda n: ( + any( + [ + self.dominates(d, m) + for m in self.dataflow_analysis.sources(self.graph, n) + ] + ) + and not self.strictly_dominates(d, n) + ), + self.graph.nodes(), + ) + ) + for d in self.dominators.keys() + } + + DominatorLatticeValue = PowersetLatticeValue[int] -class DominatorTreeAnalysis( - ForwardDataflowAnalysis[int, CFGBlock, DominatorLatticeValue] -): +class DominatorAnalysis(ForwardDataflowAnalysis[int, CFGBlock, DominatorLatticeValue]): lattice = PowersetLattice[DominatorLatticeValue]() def initial_state(self, nodes): @@ -42,15 +175,25 @@ def merge(self, states): return self.lattice.merge_meet(states) def transfer( - self, graph, node_id: int, state_in: DominatorLatticeValue + self, graph: CFG, node_id: int, state_in: DominatorLatticeValue ) -> DominatorLatticeValue: return self.lattice.join(state_in, {node_id}) + def result(self, graph, in_states, out_states, iterations): + return DominatorDataflowResult( + dataflow_analysis=self, + graph=graph, + in_states=in_states, + out_states=out_states, + iterations=iterations, + post=False, + ) + ######################################################################################## -class PostDominatorTreeAnalysis( +class PostDominatorAnalysis( BackwardDataflowAnalysis[int, CFGBlock, DominatorLatticeValue] ): lattice = PowersetLattice[DominatorLatticeValue]() @@ -68,3 +211,13 @@ def transfer( self, graph, node_id: int, state_in: DominatorLatticeValue ) -> DominatorLatticeValue: return self.lattice.join(state_in, {node_id}) + + def result(self, graph, in_states, out_states, iterations): + return DominatorDataflowResult( + dataflow_analysis=self, + graph=graph, + in_states=in_states, + out_states=out_states, + iterations=iterations, + post=True, + ) diff --git a/uv.lock b/uv.lock index 3ba9821c..8deacda8 100644 --- a/uv.lock +++ b/uv.lock @@ -1823,7 +1823,7 @@ wheels = [ [[package]] name = "oqd-compiler-infrastructure" version = "0.1.0" -source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#ef005fb92c23597d085cd57cc3bb09a1f3db0be3" } +source = { git = "https://github.com/openquantumdesign/oqd-compiler-infrastructure?branch=fix_maplattice#a90faafa585fc4c39755f1eccd06c4134e3ab844" } dependencies = [ { name = "ast-comments" }, { name = "graphviz" }, From 410d2a0178791e0c4e488175316e2f44c9a46385 Mon Sep 17 00:00:00 2001 From: yhteoh Date: Sun, 20 Sep 2026 00:00:36 -0400 Subject: [PATCH 25/25] [fix] copy blocks before converting from cfg to ast --- src/oqd_core/analysis/analog/cfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oqd_core/analysis/analog/cfg.py b/src/oqd_core/analysis/analog/cfg.py index 34a66749..6ed02ce5 100644 --- a/src/oqd_core/analysis/analog/cfg.py +++ b/src/oqd_core/analysis/analog/cfg.py @@ -265,7 +265,7 @@ def _consume(self, blocks, start=0, until=None): def map_CFG(self, model): circuit = AnalogCircuit() - statements, _ = self._consume(model.blocks) + statements, _ = self._consume(model.blocks.copy()) circuit.statements.extend(statements) return circuit