diff --git a/analysis/config_types.py b/analysis/config_types.py index bcf6d4b8..9947b460 100644 --- a/analysis/config_types.py +++ b/analysis/config_types.py @@ -66,7 +66,7 @@ def __init__(self): self.max_bosons_per_state = None def _only_once(self): if self.source is not None: - print("Already set Hamiltonian source to {self.source}.") + logger.error(f"Already set Hamiltonian source to {self.source}.") assert self.source is None def load_second_quantization(self, filename, @@ -118,7 +118,7 @@ def __init__(self): self.method = None def _only_once(self): if self.method is not None: - print("Already set unitary method to {self.method}.") + logger.error(f"Already set unitary method to {self.method}.") assert self.method is None def encode_ramped_trotter(self, **kwargs): self._only_once() @@ -538,7 +538,7 @@ def _format_results(self, obj, indent=0, max_line_length=100): def show_results(self): formatted = self._format_results(self.results, indent=0) - logger.warning(f"results:\n{formatted}") + logger.results(f"results:\n{formatted}") def _filter_for_toml(self, obj): """Recursively filter out numpy arrays and other non-serializable objects from results.""" diff --git a/analysis/driver.py b/analysis/driver.py index c6a106ab..406c6251 100644 --- a/analysis/driver.py +++ b/analysis/driver.py @@ -107,7 +107,7 @@ def run(): state.show_results() state.save_summary() - logger.warning("Analysis complete.") + logger.results("Analysis complete.") # ================================================================================================= diff --git a/analysis/error_analysis.py b/analysis/error_analysis.py index f412c8d1..ebd91658 100644 --- a/analysis/error_analysis.py +++ b/analysis/error_analysis.py @@ -139,7 +139,7 @@ def _compute_state_dependent_errors(exact_unitary_matrix, approx_unitary_matrix, try: initial_state = load_state(state_file) except Exception as e: - logger.error(f"ERROR: Failed to load state from {state_file}: {e}") + logger.error(f"Failed to load state from {state_file}: {e}") raise # Apply exact time evolution operator: U_exact |ψ⟩ diff --git a/analysis/matrix_eigendecomposition.py b/analysis/matrix_eigendecomposition.py index 1d2505fe..9f518862 100644 --- a/analysis/matrix_eigendecomposition.py +++ b/analysis/matrix_eigendecomposition.py @@ -48,8 +48,8 @@ def compute_unitary_matrix(bloq): try: return bloq.tensor_contract() except Exception as e: - logger.info( - f"ERROR: Failed to compute unitary matrix: {e}\n" + logger.error( + f"Failed to compute unitary matrix: {e}\n" "This may indicate a bug in the bloq's tensor_contract() implementation." ) raise @@ -114,7 +114,7 @@ def output_unitary_matrix(filename, unitary_matrix) -> dict: logger.verbose(f"Matrix Frobenius norm: {matrix_norm:.6e}") logger.verbose(f"Unitarity error ||U†U - I||_F: {unitarity_error:.6e}") except Exception as e: - logger.info(f"WARNING: Could not compute unitarity check: {e}") + logger.warning(f"Could not compute unitarity check: {e}") matrix_norm = None unitarity_error = None diff --git a/analysis/numerical_simulation.py b/analysis/numerical_simulation.py index 837e69ec..5f11e786 100644 --- a/analysis/numerical_simulation.py +++ b/analysis/numerical_simulation.py @@ -52,7 +52,7 @@ def numerical_simulation( try: initial_state = load_state(input_file) except Exception as e: - logger.info(f"ERROR: Failed to load state from {input_file}: {e}") + logger.error(f"Failed to load state from {input_file}: {e}") raise # Validate dimensions @@ -82,7 +82,7 @@ def numerical_simulation( try: save_state(output_file, final_state) except Exception as e: - logger.info(f"ERROR: Failed to save state to {output_file}: {e}") + logger.error(f"Failed to save state to {output_file}: {e}") raise logger.info(f" Simulation complete: {input_file} -> {output_file}") diff --git a/analysis/operators.py b/analysis/operators.py index 6107449b..278ac983 100644 --- a/analysis/operators.py +++ b/analysis/operators.py @@ -443,9 +443,6 @@ def _apply_energy_shift(self, eigenvalues: np.ndarray, operator_type: str) -> np return eigenvalues + self.energy_shift else: # time_evolution phase_factor = np.exp(-1j * self.energy_shift * self.tevol_hbar) - print(f"phase factor = {phase_factor}") - print(f"energy shift = {self.energy_shift}") - print(f"time step / hbar = {self.tevol_hbar}") return phase_factor * eigenvalues def _remove_energy_shift(self, eigenvalues: np.ndarray, operator_type: str) -> np.ndarray: @@ -456,7 +453,4 @@ def _remove_energy_shift(self, eigenvalues: np.ndarray, operator_type: str) -> n return eigenvalues - self.energy_shift else: # time_evolution phase_factor = np.exp(1j * self.energy_shift * self.tevol_hbar) - print(f"phase factor = {phase_factor}") - print(f"energy shift = {self.energy_shift}") - print(f"time step / hbar = {self.tevol_hbar}") return phase_factor * eigenvalues diff --git a/analysis/trotter_coefficients.py b/analysis/trotter_coefficients.py index 0edf8cb2..c2e774e4 100644 --- a/analysis/trotter_coefficients.py +++ b/analysis/trotter_coefficients.py @@ -11,10 +11,13 @@ For production use, prefer trotter_coefficients_fast.py which is 100-150x faster. """ +import logging import time import numpy as np import math +logger = logging.getLogger(__name__) + # -------------------------------------------------- # Helper functions for Pauli string arithmetic # -------------------------------------------------- @@ -163,7 +166,7 @@ def trotter_error_estimator(pauli_terms, time_limit, batch_size=100): break total_C1 = N * (N - 1)/2 # Total number of distinct pairs (i < j) C1_est = C1_sum * (total_C1 / samples_C1) if samples_C1 > 0 else 0.0 - print(f" C1: {samples_C1} samples") + logger.debug(f"C1: {samples_C1} samples") # --------------------------- # Estimate C21 = sum_{k 0 else 0.0 - print(f" C21: {samples_C21} samples") + logger.debug(f"C21: {samples_C21} samples") # --------------------------- # Estimate C22 = sum_{k 0 else 0.0 - print(f" C22: {samples_C22} samples") + logger.debug(f"C22: {samples_C22} samples") # --------------------------- # Final output diff --git a/analysis/unitary.py b/analysis/unitary.py index ec8db38a..928afca7 100644 --- a/analysis/unitary.py +++ b/analysis/unitary.py @@ -35,17 +35,11 @@ def __init__(self, hamiltonian, prepare_type=None, probability_eps=0.002, **kwar n_terms = len(pauli_terms) n_tot = 2**(int(np.ceil(np.log2(n_terms)))) n_pad = n_tot - n_terms -# print(f"-- n_terms={n_terms}") -# print(f"-- n_tot={n_tot}") -# for i in range(n_terms): -# print(f"-- getTerms[{i}]={pauli_terms[i]}") alphas = [np.sqrt(np.abs(t.coefficient)) for t in pauli_terms] alpha = np.sum([a**2 for a in alphas]) -# print(f"-- alpha={alpha}") alphas_scaled = [a/np.sqrt(alpha) for a in alphas] alphas_scaled.extend([0.0 for i in range(n_pad)]) -# print(f"-- alphas_scaled={alphas_scaled}") selection_bitsize = int(np.ceil(np.log2(n_tot))) diff --git a/common/logging_utils.py b/common/logging_utils.py index 35dc15f0..8a8562d1 100644 --- a/common/logging_utils.py +++ b/common/logging_utils.py @@ -24,36 +24,43 @@ from typing import Optional -# Custom log level between INFO and DEBUG -VERBOSE = 25 # logging.INFO=20, logging.WARNING=30 +# Custom log levels +# Lower values = more verbose in Python logging +VERBOSE = 15 # Between DEBUG (10) and INFO (20) +RESULTS = 45 # Between ERROR (40) and CRITICAL (50) - for critical output that should almost always be shown -def add_verbose_level(): +def add_custom_levels(): """ - Add VERBOSE log level to logging module. + Add custom log levels to logging module. - This creates a new log level between INFO and WARNING for - moderately detailed logging that's more than INFO but not as - verbose as DEBUG. + VERBOSE: Between DEBUG and INFO for moderately detailed logging. + RESULTS: Between ERROR and CRITICAL for critical output (results, completion) + that should be shown unless user explicitly sets level to CRITICAL. """ - # Only add if not already present - if hasattr(logging, 'VERBOSE'): - return + # Add VERBOSE level + if not hasattr(logging, 'VERBOSE'): + logging.addLevelName(VERBOSE, "VERBOSE") - logging.addLevelName(VERBOSE, "VERBOSE") + def verbose(self, message, *args, **kwargs): + if self.isEnabledFor(VERBOSE): + self._log(VERBOSE, message, args, **kwargs) - def verbose(self, message, *args, **kwargs): - if self.isEnabledFor(VERBOSE): - self._log(VERBOSE, message, args, **kwargs) + logging.Logger.verbose = verbose + logging.verbose = lambda msg, *args, **kwargs: logging.log(VERBOSE, msg, *args, **kwargs) + logging.VERBOSE = VERBOSE - # Add verbose() method to Logger class - logging.Logger.verbose = verbose + # Add RESULTS level + if not hasattr(logging, 'RESULTS'): + logging.addLevelName(RESULTS, "RESULTS") - # Add module-level verbose() function - logging.verbose = lambda msg, *args, **kwargs: logging.log(VERBOSE, msg, *args, **kwargs) + def results(self, message, *args, **kwargs): + if self.isEnabledFor(RESULTS): + self._log(RESULTS, message, args, **kwargs) - # Add VERBOSE constant - logging.VERBOSE = VERBOSE + logging.Logger.results = results + logging.results = lambda msg, *args, **kwargs: logging.log(RESULTS, msg, *args, **kwargs) + logging.RESULTS = RESULTS def configure_logging( @@ -89,8 +96,8 @@ def configure_logging( >>> logger = logging.getLogger(__name__) >>> logger.info("Application started") """ - # Add custom VERBOSE level - add_verbose_level() + # Add custom levels (VERBOSE and RESULTS) + add_custom_levels() # Parse level string level_map = {