QWARD Architecture¶
This document outlines the architecture of the QWARD library and provides usage examples.
Overview¶
QWARD is designed with a clear separation between execution and analysis components. The architecture consists of four main components:
classDiagram
class Scanner {
+circuit: QuantumCircuit
+job: Union[AerJob, RuntimeJob]
+result: Result
+metrics: List[Metric]
+__init__(circuit, job, result)
+add_metric(metric)
+calculate_metrics()
}
class QiskitRuntimeService {
<<extends>>
+circuit: QuantumCircuit
+backend: Union[Backend, str]
+job: Union[AerJob, RuntimeJob]
+result: Result
+__init__(circuit, backend)
+run()
+check_status()
+get_results()
+run_and_watch()
}
class Result {
+job: Union[AerJob, RuntimeJob]
+quasi_dists: List[Dict]
+metadata: Dict
+__init__(job, quasi_dists, metadata)
+save(path)
+load(path)
}
class Metric {
<<abstract>>
+_metric_type: MetricsType
+_id: MetricsId
+_circuit: QuantumCircuit
+__init__(circuit)
+metric_type: MetricsType
+id: MetricsId
+name: str
+circuit: QuantumCircuit
+_get_metric_type()
+is_ready()
+get_metrics()
}
class MetricsType {
<<enumeration>>
PRE_RUNTIME
POST_RUNTIME
}
class MetricsId {
<<enumeration>>
QISKIT
COMPLEXITY
SUCCESS_RATE
+get_default_metrics()
}
class QiskitMetrics {
+__init__(circuit)
+_get_metric_type()
+is_ready()
+get_metrics()
+get_basic_metrics()
+get_instruction_metrics()
+get_scheduling_metrics()
+get_all_metrics()
}
class ComplexityMetrics {
+__init__(circuit)
+_get_metric_type()
+is_ready()
+get_metrics()
}
class SuccessRate {
+_job: Union[AerJob, RuntimeJob]
+_result: Result
+__init__(circuit, job, result)
+_get_metric_type()
+is_ready()
+get_metrics()
}
Scanner --> Result
Scanner --> Metric
QiskitRuntimeService --> Result
Metric <|-- QiskitMetrics
Metric <|-- ComplexityMetrics
Metric <|-- SuccessRate
Metric --> MetricsType
Metric --> MetricsId
Folder Structure¶
The QWARD library is organized into the following folder structure:
/qward/
├── __init__.py # Main package initialization
├── scanner.py # Scanner class implementation
├── runtime/
│ ├── __init__.py
│ ├── qiskit_runtime.py # QiskitRuntimeService implementation
│ └── device.py # Device-specific implementations
├── result.py # Result class implementation
├── metrics/
│ ├── __init__.py
│ ├── base_metric.py # Base Metric class
│ ├── types.py # MetricsType and MetricsId enums
│ ├── qiskit_metrics.py # QiskitMetrics implementation
│ ├── complexity_metrics.py # ComplexityMetrics implementation
│ └── success_rate.py # SuccessRate implementation
├── utils/
│ ├── __init__.py
│ └── helpers.py # Utility functions
└── examples/
├── __init__.py
├── basic_usage.py # Basic usage examples
├── custom_metrics.py # Examples of creating custom metrics
├── runtime_execution.py # Examples of using QiskitRuntimeService
└── result_analysis.py # Examples of analyzing results
This structure provides a clean organization for the code, with:
Main Package: Core classes at the top level for easy imports
Runtime Module: Handles execution of quantum circuits
Metrics Module: Contains all metric implementations
Utils Module: Helper functions and utilities
Examples Module: Working code examples demonstrating library usage
Components¶
Scanner¶
The Scanner class is the main entry point for analyzing quantum circuits. It takes a circuit, job, and result as input and allows users to add and calculate metrics.
QiskitRuntimeService¶
The QiskitRuntimeService class extends Qiskit’s QiskitRuntimeService
class to provide enhanced functionality for quantum circuit execution. It inherits all standard Qiskit runtime capabilities and adds the run_and_watch
method for improved job monitoring. This class manages the job lifecycle and result collection, providing a streamlined interface for executing circuits on IBM quantum hardware.
Result¶
The Result class represents the output of a quantum circuit execution. It includes the job information, quasi-probability distributions, and metadata. It provides methods for saving and loading results, as well as updating results from a job.
Metric¶
The Metric class is an abstract base class that defines the interface for all metrics. It includes the circuit attribute, properties for metric type and ID, and abstract methods for metric calculation. Concrete implementations include QiskitMetrics, ComplexityMetrics, and SuccessRate.
MetricsType¶
The MetricsType enum defines the different types of metrics available, such as PRE_RUNTIME and POST_RUNTIME.
MetricsId¶
The MetricsId enum defines the different IDs for metrics, such as QISKIT, COMPLEXITY, and SUCCESS_RATE. It also provides a method to get the default metric classes.
SuccessRate¶
The SuccessRate class calculates success rate metrics for quantum circuits, including success rate, fidelity, and error rate. These metrics are calculated based on the quasi-probability distributions from the job result.
Usage Examples¶
Basic Circuit Analysis¶
from qiskit import QuantumCircuit
from qward import Scanner, QiskitMetrics
# Create a quantum circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
# Create a scanner with the circuit
scanner = Scanner(circuit=circuit)
# Add a metric
scanner.add_metric(QiskitMetrics(circuit))
# Calculate metrics
results = scanner.calculate_metrics()
Running Circuits with QiskitRuntimeService¶
from qiskit import QuantumCircuit
from qward import QiskitRuntimeService
# Create a quantum circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
# Create a runtime service instance
runtime_service = QiskitRuntimeService(circuit=circuit, backend="ibmq_qasm_simulator")
# Run the circuit with automatic status monitoring
# This will:
# 1. Generate a preset pass manager for the backend
# 2. Create a sampler for the circuit
# 3. Run the circuit and monitor its status
# 4. Return the results when complete
result = runtime_service.run_and_watch()
# Or use the standard approach
runtime_service.run()
status = runtime_service.check_status()
result = runtime_service.get_results()
Analyzing Results¶
from qward import Scanner, QiskitMetrics, ComplexityMetrics
# Create a scanner with a result
scanner = Scanner(result=result)
# Add multiple metrics
scanner.add_metric(QiskitMetrics(circuit))
scanner.add_metric(ComplexityMetrics(circuit))
# Calculate metrics
results = scanner.calculate_metrics()
Using Custom Metrics¶
from qward import Metric, MetricsType, MetricsId
class MyCustomMetric(Metric):
def __init__(self, circuit):
super().__init__(circuit)
def _get_metric_type(self) -> MetricsType:
"""
Get the type of this metric.
Returns:
MetricsType: The type of this metric
"""
return MetricsType.PRE_RUNTIME
def is_ready(self):
return True
def get_metrics(self):
# Custom metric calculation
return {"my_metric": value}
# Use the custom metric
scanner = Scanner(circuit=circuit)
scanner.add_metric(MyCustomMetric(circuit))
results = scanner.calculate_metrics()
Best Practices¶
Circuit Analysis
Use the Scanner class for all circuit analysis
Add metrics before calculating results
Consider using multiple metrics for comprehensive analysis
Execution
Use QiskitRuntimeService for IBM backend execution
Use run_and_watch() for simplified job monitoring and execution
The run_and_watch() method handles:
Circuit transpilation with preset pass manager
Sampler creation and configuration
Job submission and monitoring
Result collection and processing
Handle job and result errors appropriately
Result Management
Save results for later analysis
Include relevant metadata with results
Use consistent naming conventions for saved results
Custom Metrics
Inherit from the Metric base class
Implement the required abstract methods
Return results in a consistent format
Document metric calculation methodology