Source code for qarp.operators._qdrift

from typing import Dict, Optional, Tuple

import numpy as np

from qarp.operators._qubit_operator import QubitOperator


[docs] class qDRIFT: def __init__( self, H: QubitOperator, samples: int, ratio: Optional[float] = None, verbose: bool = False, seed: Optional[int] = None, ): """ Implements qDRIFT approximation for QubitOperators. qDRIFT provides a randomized compilation technique that approximates a Hamiltonian by sampling its Pauli terms according to their weights. Supports both fully randomized and partially randomized variants. References: Campbell, E. (2019). Random compiler for fast Hamiltonian simulation. Physical Review Letters, 123(7), 070503. Args: H: QubitOperator to approximate samples: Number of samples to draw from the operator ratio: Fraction of terms to keep deterministically for partially randomized qDRIFT (between 0 and 1). If None, uses fully randomized qDRIFT. verbose: If True, print diagnostic information seed: Optional seed for this instance's local random generator Raises: ValueError: If samples < 0 or ratio not in [0, 1] TypeError: If H is not a QubitOperator """ if not isinstance(H, QubitOperator): raise TypeError(f"H must be a QubitOperator, got {type(H)}") if not isinstance(samples, int) or samples < 0: raise ValueError(f"samples must be a positive integer, got {samples}") if ratio is not None and (not isinstance(ratio, float) or not 0 <= ratio <= 1): raise ValueError(f"ratio must be a float between 0 and 1, got {ratio}") self.H = H self.samples = samples self.ratio = ratio self.verbose = verbose self.seed = seed self._rng = np.random.default_rng(seed) def _compute_normalization(self) -> Tuple[float, Dict, Dict]: """Compute normalization constant and probability distributions. Returns: Tuple containing: - lambda_val: 1-norm of coefficients - probabilities: Dict mapping terms to their probabilities - terms_normalized: Dict mapping terms to normalized coefficients """ terms = self.H.terms.items() # Compute 1-norm (lambda) lambda_val = sum(abs(coeff) for _, coeff in terms) if lambda_val == 0: raise ValueError("Hamiltonian has zero norm") # Build probability distribution and normalized terms probabilities = {term: abs(coeff) / lambda_val for term, coeff in terms} terms_normalized = {term: coeff / lambda_val for term, coeff in terms} return lambda_val, probabilities, terms_normalized
[docs] def qdrift(self) -> QubitOperator: """Apply fully randomized qDRIFT approximation. Samples Pauli terms according to their weight distribution and constructs an approximate Hamiltonian. Returns: Approximate QubitOperator with sampled terms Raises: ValueError: If samples is 0 (at least 1 sample required) """ if self.samples == 0: raise ValueError( "qdrift() requires at least 1 sample. Use partially_randomized() for deterministic-only mode." ) lambda_val, probabilities, terms_normalized = self._compute_normalization() # Extract terms and probabilities terms_list = list(probabilities.keys()) probs_list = np.array(list(probabilities.values())) # Normalize probabilities probs_list /= np.sum(probs_list) # Sample terms according to probabilities sampled_indices = self._rng.choice( len(terms_list), size=self.samples, replace=True, p=probs_list ) sampled_terms = [terms_list[int(index)] for index in sampled_indices] sampled_terms_unique = list(dict.fromkeys(sampled_terms)) if self.verbose: print(f"Sampled {len(sampled_terms_unique)} unique terms out of {self.samples} samples") print(f"Original Hamiltonian had {len(terms_list)} terms") # Build effective Hamiltonian Heff = QubitOperator() for term in sampled_terms_unique: Heff += QubitOperator(term, terms_normalized[term]) return lambda_val * Heff
[docs] def partially_randomized(self) -> QubitOperator: """Apply partially randomized qDRIFT approximation. Keeps the most significant terms deterministically and samples the remaining terms randomly according to their weights. Returns: Approximate QubitOperator with deterministic + sampled terms Raises: ValueError: If ratio was not provided during initialization """ if self.ratio is None: raise ValueError( "ratio parameter required for partially randomized qDRIFT. " "Provide ratio in __init__ or use qdrift() method instead." ) lambda_val, probabilities, terms_normalized = self._compute_normalization() # Sort terms by probability (descending) sorted_terms = sorted(probabilities.items(), key=lambda x: x[1], reverse=True) # Split into deterministic and random parts num_deterministic = max(1, round(self.ratio * len(sorted_terms))) deterministic_terms = dict(sorted_terms[:num_deterministic]) random_terms = dict(sorted_terms[num_deterministic:]) # Build deterministic Hamiltonian H_deterministic = QubitOperator() for term in deterministic_terms: H_deterministic += QubitOperator(term, terms_normalized[term]) # Compute lambda for random part (hoist the .terms view out of the # genexpr — the attribute access re-validates the cached dict view on # every iteration otherwise) h_terms = self.H.terms lambda_random = sum(abs(h_terms[term]) for term in random_terms) / lambda_val if self.verbose: print(f"Deterministic terms: {num_deterministic}/{len(sorted_terms)}") print(f"λ_random = {lambda_random:.4f} (should be << 1 for optimal performance)") # Sample from random terms if random_terms and self.samples > 0: terms_list = list(random_terms.keys()) probs_list = np.array(list(random_terms.values())) probs_list /= np.sum(probs_list) # Normalize sampled_indices = self._rng.choice( len(terms_list), size=self.samples, replace=True, p=probs_list ) sampled_random = [terms_list[int(index)] for index in sampled_indices] sampled_random_unique = list(dict.fromkeys(sampled_random)) # Build random Hamiltonian H_random = QubitOperator() for term in sampled_random_unique: H_random += QubitOperator(term, terms_normalized[term]) if self.verbose: print(f"Sampled {len(sampled_random_unique)} random terms") else: H_random = QubitOperator() if self.verbose: if self.samples == 0: print("No sampling (samples=0, using deterministic terms only)") else: print("No random terms to sample (all terms kept deterministically)") # Combine deterministic and random parts Heff = H_deterministic + H_random return lambda_val * Heff