Error Handling

Comprehensive error handling system for LogosQ using Result types and custom error enums.

Overview

The error module provides a robust error handling system for the LogosQ library. All operations that can fail return Result<T, LogosQError> instead of panicking, allowing for proper error propagation and handling.

Error Type

pub enum LogosQError {
    InvalidQubitIndex { index: usize, num_qubits: usize },
    InvalidStateDimension { dimension: usize },
    DimensionMismatch { expected: usize, actual: usize },
    InvalidGateMatrix { expected: Vec<usize>, actual: Vec<usize> },
    ParameterCountMismatch { expected: usize, actual: usize },
    CircuitQubitMismatch { circuit_qubits: usize, state_qubits: usize },
    InvalidProbability { value: f64 },
    InvalidAngle { value: f64 },
    MeasurementError { message: String },
    OptimizationError { message: String },
    GradientError { message: String },
}

Result Type Alias

For convenience, the module provides a type alias:

pub type Result<T> = std::result::Result<T, LogosQError>;

Error Variants

InvalidQubitIndex

Occurs when a qubit index is out of range.

// Error message: "Invalid qubit index: 5 (must be < 3)"
LogosQError::InvalidQubitIndex { index: 5, num_qubits: 3 }

Example:

use logosq::prelude::*;

let mut circuit = Circuit::new(3);
match circuit.x(5) {
    Ok(_) => println!("Gate added"),
    Err(LogosQError::InvalidQubitIndex { index, num_qubits }) => {
        eprintln!("Qubit {} is invalid for {} qubit circuit", index, num_qubits);
    }
    Err(e) => eprintln!("Other error: {}", e),
}

InvalidStateDimension

Occurs when a state vector has an invalid dimension (not a power of 2).

// Error message: "Invalid state dimension: 5 (must be a power of 2)"
LogosQError::InvalidStateDimension { dimension: 5 }

DimensionMismatch

Occurs when dimensions don't match expectations.

// Error message: "Dimension mismatch: expected 4, got 8"
LogosQError::DimensionMismatch { expected: 4, actual: 8 }

CircuitQubitMismatch

Occurs when executing a circuit on a state with different qubit counts.

// Error message: "Circuit qubit count mismatch: circuit has 3, state has 2"
LogosQError::CircuitQubitMismatch { circuit_qubits: 3, state_qubits: 2 }

Error Handling Patterns

Using the ? Operator

The most common pattern for error handling:

use logosq::prelude::*;

fn create_bell_state() -> Result<State> {
    let mut circuit = Circuit::new(2);
    circuit.h(0)?;  // Propagates error if qubit index is invalid
    circuit.cnot(0, 1)?;
    
    let mut state = State::zero_state(2);
    circuit.execute(&mut state)?;
    Ok(state)
}

Pattern Matching

For more detailed error handling:

use logosq::prelude::*;

match circuit.add_operation(gate, vec![5], "Gate") {
    Ok(_) => println!("Success"),
    Err(LogosQError::InvalidQubitIndex { index, num_qubits }) => {
        eprintln!("Invalid qubit {} for {} qubit circuit", index, num_qubits);
    }
    Err(e) => eprintln!("Error: {}", e),
}

Unwrapping (for Examples/Testing)

In examples or tests where errors are unexpected:

use logosq::prelude::*;

let mut circuit = Circuit::new(2);
circuit.h(0).unwrap();  // Panics on error - use only when error is impossible
circuit.cnot(0, 1).unwrap();

Converting to Option

If you want to ignore errors:

use logosq::prelude::*;

let result = circuit.add_operation(gate, vec![0], "Gate").ok();
if result.is_some() {
    println!("Gate added successfully");
}

Best Practices

  1. Always handle Results: Don't ignore Result return types
  2. Use ? for error propagation: In functions that return Result
  3. Provide context: When handling errors, log or display helpful messages
  4. Match specific errors: Use pattern matching for different error types
  5. Avoid unwrap in production: Use unwrap() only in tests or examples

Examples

Complete Error Handling Example

use logosq::prelude::*;

fn quantum_algorithm() -> Result<()> {
    // Create circuit
    let mut circuit = Circuit::new(3)?;
    
    // Add gates with error handling
    circuit.h(0)?;
    circuit.cnot(0, 1)?;
    circuit.ry(2, std::f64::consts::PI / 4.0)?;
    
    // Create and execute state
    let mut state = State::new(
        Array1::from_vec(vec![Complex64::new(1.0, 0.0); 8]),
        Some(3)
    )?;
    
    circuit.execute(&mut state)?;
    
    // Measure with error handling
    let result = state.measure_qubit(0)?;
    println!("Measurement: {}", result);
    
    Ok(())
}

fn main() {
    match quantum_algorithm() {
        Ok(_) => println!("Algorithm completed successfully"),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Error Handling in Loops

use logosq::prelude::*;

fn add_gates_safely(circuit: &mut Circuit, qubits: &[usize]) -> Result<()> {
    for &qubit in qubits {
        circuit.h(qubit)?;  // Stops on first error
    }
    Ok(())
}

Integration with Other Modules

All modules in LogosQ use LogosQError for consistent error handling:

  • State: State::new(), measure_qubit(), tensor_product() return Result
  • Circuit: execute(), add_operation(), compose() return Result
  • Gates: Gate::apply() returns Result
  • Optimization: VQE, gradient methods return Result

This unified error system makes it easy to handle errors across the entire library consistently.