CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-openqasm3

Reference OpenQASM AST in Python - contains the reference abstract syntax tree (AST) for representing OpenQASM 3 programs, tools to parse text into this AST, and tools to manipulate the AST

Pending
Overview
Eval results
Files

visitors.mddocs/

AST Visitors and Transformers

Visitor and transformer patterns for systematic AST traversal, analysis, and modification with optional context support. These classes enable powerful AST manipulation patterns including tree walking, analysis, transformation, and code generation.

Capabilities

Base Visitor Class

Generic visitor pattern for AST traversal with flexible context support.

class QASMVisitor(Generic[T]):
    """
    A node visitor base class that walks the abstract syntax tree and calls a
    visitor function for every node found. This function may return a value
    which is forwarded by the `visit` method.

    The optional context argument allows the visitor to hold temporary state
    while visiting nodes without modifying the AST or the visitor instance.
    """

    def visit(self, node: QASMNode, context: Optional[T] = None):
        """
        Visit a node and dispatch to the appropriate visitor method.
        
        Args:
            node: AST node to visit
            context: Optional context object for holding temporary state
        
        Returns:
            Result from the visitor method (if any)
        """

    def generic_visit(self, node: QASMNode, context: Optional[T] = None):
        """
        Called if no explicit visitor function exists for a node.
        
        This method recursively visits all child nodes in the AST.
        Override this method to change the default traversal behavior.
        
        Args:
            node: AST node to visit
            context: Optional context object
        
        Returns:
            None by default, but can be overridden
        """

AST Transformer Class

Visitor subclass that enables modification of AST nodes during traversal.

class QASMTransformer(QASMVisitor[T]):
    """
    A QASMVisitor subclass that allows modification of AST nodes during traversal.
    
    This class enables tree transformation operations including node replacement,
    deletion, and list modifications. It handles the complexities of maintaining
    valid AST structure during transformations.
    """

    def generic_visit(self, node: QASMNode, context: Optional[T] = None) -> QASMNode:
        """
        Visit and potentially modify nodes and their children.
        
        This method visits all child nodes and can replace them with modified
        versions. It handles lists, single nodes, and None values appropriately.
        
        Args:
            node: AST node to visit and potentially transform
            context: Optional context object
        
        Returns:
            The node (potentially modified) or a replacement node
        """

Usage Examples

Basic AST Analysis

from openqasm3 import ast, visitor
import openqasm3

class GateAnalyzer(visitor.QASMVisitor):
    """Analyze quantum gates in a program"""
    
    def __init__(self):
        self.gate_calls = []
        self.gate_definitions = []
        self.total_qubits = 0
    
    def visit_QuantumGate(self, node):
        """Visit quantum gate calls"""
        self.gate_calls.append({
            'name': node.name.name,
            'qubits': len(node.qubits),
            'parameters': len(node.arguments)
        })
        self.generic_visit(node)
    
    def visit_QuantumGateDefinition(self, node):
        """Visit quantum gate definitions"""
        self.gate_definitions.append({
            'name': node.name.name,
            'qubits': len(node.qubits),
            'parameters': len(node.arguments)
        })
        self.generic_visit(node)
    
    def visit_QubitDeclaration(self, node):
        """Count total qubits declared"""
        if node.size:
            # Array declaration like qubit[4] q
            if isinstance(node.size, ast.IntegerLiteral):
                self.total_qubits += node.size.value
        else:
            # Single qubit declaration like qubit q
            self.total_qubits += 1
        self.generic_visit(node)

# Use the analyzer
qasm_source = '''
OPENQASM 3.0;
qubit[3] q;
gate bell q0, q1 {
    h q0;
    cx q0, q1;
}
bell q[0], q[1];
h q[2];
'''

program = openqasm3.parse(qasm_source)
analyzer = GateAnalyzer()
analyzer.visit(program)

print(f"Total qubits: {analyzer.total_qubits}")
print(f"Gate definitions: {analyzer.gate_definitions}")
print(f"Gate calls: {analyzer.gate_calls}")

AST Transformation

from openqasm3 import ast, visitor
import openqasm3

class GateInverter(visitor.QASMTransformer):
    """Transform gates to add inverse modifiers"""
    
    def visit_QuantumGate(self, node):
        """Add inverse modifier to all gates"""
        # Create inverse modifier
        inv_modifier = ast.QuantumGateModifier(
            modifier=ast.GateModifierName.inv,
            argument=None
        )
        
        # Create new gate with inverse modifier
        new_gate = ast.QuantumGate(
            modifiers=[inv_modifier] + node.modifiers,
            name=node.name,
            arguments=node.arguments,
            qubits=node.qubits,
            duration=node.duration
        )
        
        return self.generic_visit(new_gate)

# Transform a program
program = openqasm3.parse('''
OPENQASM 3.0;
qubit[2] q;
h q[0];
cx q[0], q[1];
''')

transformer = GateInverter()
transformed_program = transformer.visit(program)

# Convert back to text to see the result
result = openqasm3.dumps(transformed_program)
print(result)

Context-Based Analysis

from openqasm3 import ast, visitor
import openqasm3
from typing import List, Set

class ScopeInfo:
    """Context object to track scope information"""
    def __init__(self):
        self.declared_variables: Set[str] = set()
        self.used_variables: Set[str] = set()
        self.scope_level: int = 0

class VariableAnalyzer(visitor.QASMVisitor[ScopeInfo]):
    """Analyze variable usage with scope tracking"""
    
    def visit_Program(self, node, context=None):
        """Start analysis with global scope"""
        if context is None:
            context = ScopeInfo()
        
        self.generic_visit(node, context)
        
        # Report unused variables
        unused = context.declared_variables - context.used_variables
        if unused:
            print(f"Unused variables: {unused}")
    
    def visit_ClassicalDeclaration(self, node, context):
        """Track variable declarations"""
        var_name = node.identifier.name
        context.declared_variables.add(var_name)
        print(f"Declared variable '{var_name}' at scope level {context.scope_level}")
        
        # Visit initialization expression
        if node.init_expression:
            self.visit(node.init_expression, context)
    
    def visit_Identifier(self, node, context):
        """Track variable usage"""
        var_name = node.name
        if var_name in context.declared_variables:
            context.used_variables.add(var_name)
        
        self.generic_visit(node, context)
    
    def visit_SubroutineDefinition(self, node, context):
        """Enter new scope for subroutine"""
        # Create new scope context
        new_context = ScopeInfo()
        new_context.declared_variables = context.declared_variables.copy()
        new_context.used_variables = context.used_variables.copy()
        new_context.scope_level = context.scope_level + 1
        
        # Add subroutine parameters to scope
        for arg in node.arguments:
            if isinstance(arg, ast.ClassicalArgument):
                new_context.declared_variables.add(arg.name.name)
        
        # Visit subroutine body with new context
        for stmt in node.body:
            self.visit(stmt, new_context)
        
        # Merge used variables back to parent context
        context.used_variables.update(new_context.used_variables)

# Analyze a program with variable usage
program = openqasm3.parse('''
OPENQASM 3.0;
int[32] x = 42;
int[32] y = 10;
int[32] result;

def int[32] add_numbers(int[32] a, int[32] b) {
    return a + b;
}

result = add_numbers(x, y);
''')

analyzer = VariableAnalyzer()
analyzer.visit(program)

Custom Visitor for Code Generation

from openqasm3 import ast, visitor
import openqasm3

class QuantumCircuitExtractor(visitor.QASMVisitor):
    """Extract quantum circuit information"""
    
    def __init__(self):
        self.circuit_info = {
            'qubits': {},
            'gates': [],
            'measurements': []
        }
    
    def visit_QubitDeclaration(self, node):
        """Extract qubit information"""
        qubit_name = node.qubit.name
        if node.size:
            if isinstance(node.size, ast.IntegerLiteral):
                size = node.size.value
            else:
                size = f"expr({node.size})"
        else:
            size = 1
        
        self.circuit_info['qubits'][qubit_name] = size
        self.generic_visit(node)
    
    def visit_QuantumGate(self, node):
        """Extract gate operations"""
        gate_info = {
            'name': node.name.name,
            'qubits': [],
            'parameters': [],
            'modifiers': []
        }
        
        # Extract qubit targets
        for qubit in node.qubits:
            if isinstance(qubit, ast.Identifier):
                gate_info['qubits'].append(qubit.name)
            elif isinstance(qubit, ast.IndexedIdentifier):
                qubit_ref = f"{qubit.name.name}[{qubit.indices}]"
                gate_info['qubits'].append(qubit_ref)
        
        # Extract parameters
        for param in node.arguments:
            if isinstance(param, ast.Identifier):
                gate_info['parameters'].append(param.name)
            elif isinstance(param, ast.FloatLiteral):
                gate_info['parameters'].append(param.value)
            elif isinstance(param, ast.IntegerLiteral):
                gate_info['parameters'].append(param.value)
        
        # Extract modifiers
        for modifier in node.modifiers:
            mod_info = {'type': modifier.modifier.name}
            if modifier.argument:
                mod_info['argument'] = str(modifier.argument)
            gate_info['modifiers'].append(mod_info)
        
        self.circuit_info['gates'].append(gate_info)
        self.generic_visit(node)
    
    def visit_QuantumMeasurementStatement(self, node):
        """Extract measurement operations"""
        measurement_info = {
            'qubit': str(node.measure.qubit),
            'target': str(node.target) if node.target else None
        }
        self.circuit_info['measurements'].append(measurement_info)
        self.generic_visit(node)

# Extract circuit information
program = openqasm3.parse('''
OPENQASM 3.0;
qubit[3] q;
bit[3] c;

h q[0];
cx q[0], q[1];
ry(pi/4) q[2];
measure q[0] -> c[0];
measure q[1] -> c[1];
''')

extractor = QuantumCircuitExtractor()
extractor.visit(program)

import json
print(json.dumps(extractor.circuit_info, indent=2))

Advanced Transformation with Error Handling

from openqasm3 import ast, visitor
import openqasm3

class SafeGateReplacer(visitor.QASMTransformer):
    """Safely replace gate names with error handling"""
    
    def __init__(self, replacement_map):
        self.replacement_map = replacement_map
        self.warnings = []
    
    def visit_QuantumGate(self, node):
        """Replace gate names according to mapping"""
        original_name = node.name.name
        
        if original_name in self.replacement_map:
            new_name = self.replacement_map[original_name]
            
            # Create new gate with replaced name
            new_gate = ast.QuantumGate(
                modifiers=node.modifiers,
                name=ast.Identifier(new_name),
                arguments=node.arguments,
                qubits=node.qubits,
                duration=node.duration
            )
            
            print(f"Replaced gate '{original_name}' with '{new_name}'")
            return self.generic_visit(new_gate)
        else:
            # Gate not in replacement map
            self.warnings.append(f"Gate '{original_name}' not found in replacement map")
            return self.generic_visit(node)

# Use the transformer
replacements = {
    'h': 'hadamard',
    'cx': 'cnot',
    'x': 'pauli_x'
}

program = openqasm3.parse('''
OPENQASM 3.0;
qubit[2] q;
h q[0];
cx q[0], q[1];
y q[1];  // This gate won't be replaced
''')

replacer = SafeGateReplacer(replacements)
transformed = replacer.visit(program)

print("\nWarnings:")
for warning in replacer.warnings:
    print(f"  {warning}")

print("\nTransformed program:")
print(openqasm3.dumps(transformed))

Advanced Patterns

Combining Multiple Visitors

from openqasm3 import visitor

class CompositeAnalyzer(visitor.QASMVisitor):
    """Combine multiple analysis passes"""
    
    def __init__(self):
        self.analyzers = [
            GateAnalyzer(),
            VariableAnalyzer(),
            QuantumCircuitExtractor()
        ]
    
    def visit(self, node, context=None):
        # Run all analyzers
        for analyzer in self.analyzers:
            analyzer.visit(node, context)
        
        # Run default visit
        return super().visit(node, context)

Visitor with State Management

class StatefulVisitor(visitor.QASMVisitor):
    """Visitor that maintains complex state"""
    
    def __init__(self):
        self.state_stack = []
        self.current_function = None
        self.call_graph = {}
    
    def _push_state(self, state_name):
        self.state_stack.append(state_name)
    
    def _pop_state(self):
        if self.state_stack:
            return self.state_stack.pop()
        return None
    
    def visit_SubroutineDefinition(self, node):
        self._push_state('subroutine')
        old_function = self.current_function
        self.current_function = node.name.name
        
        self.generic_visit(node)
        
        self.current_function = old_function
        self._pop_state()

The visitor pattern provides a powerful and flexible way to work with OpenQASM 3 ASTs, enabling everything from simple analysis to complex transformations while maintaining clean, readable code.

Install with Tessl CLI

npx tessl i tessl/pypi-openqasm3

docs

ast-nodes.md

code-generation.md

index.md

parser.md

utilities.md

visitors.md

tile.json