# Wigner Simulator

A Python package for simulating quantum circuits with qutrits (three-level quantum systems) using discrete Wigner function representations. This simulator implements a hidden variable model based on the phase space formulation of quantum mechanics for qudits.

## Overview

This package provides a classical simulation framework for qutrit quantum circuits by representing quantum states through their discrete Wigner functions. Unlike traditional state vector simulators, this approach uses a phase space representation where quantum states are described by quasiprobability distributions over a discrete phase space.

The simulator supports:
- Preparation of qutrit states via Wigner functions
- Single-qutrit clifford gates (Z, Hadamard)
- Two-qutrit clifford gate (Controlled-X)
- Computational basis measurements
- Efficient batch sampling for statistical simulations
- Visualization of measurement statistics

## Installation

```bash
pip install wigner_simulator
```

## Requirements

- Python 3.8+
- NumPy
- Matplotlib

## Quick Start

```python
import numpy as np
from wigner_simulator import (
    single_qutrit_phase_space,
    wigner_from_state,
    sample_hidden_variables_batch,
    H_gate_batch,
    CX_gate_batch,
    Measure_batch,
    precompute_measurement_probs,
    plot_joint_probs
)

# Initialize phase space operators
A_ops = single_qutrit_phase_space()

# Prepare initial state (e.g., |0⟩|0⟩)
rho_0 = np.outer([1,0,0], [1,0,0])
W1 = wigner_from_state(rho_0, A_ops)
W2 = wigner_from_state(rho_0, A_ops)

# Sample hidden variables
num_runs = 10000
W_flat_list = [np.real(W1).flatten(), np.real(W2).flatten()]
x, y = sample_hidden_variables_batch(W_flat_list, num_runs)

# Apply gates
H_gate_batch(x, y, 0)
CX_gate_batch(x, y, 0, 1)

# Measure
meas_probs = precompute_measurement_probs(A_ops)
outcomes = Measure_batch(x, y, meas_probs)

# Visualize results
plot_joint_probs(outcomes, d=3, plot_type='bar')
```

## Theoretical Background

### Discrete Wigner Functions

For a d-dimensional quantum system (here d=3 for qutrits), the discrete Wigner function provides a phase space representation over a d×d discrete lattice. Each quantum state ρ is mapped to a function W(x,y) where x,y ∈ {0,1,2}.

The Wigner function is defined through a set of phase point operators A(x,y) that satisfy:

```
ρ = Σ W(x,y) A(x,y)
```

For pure quantum states, the Wigner function may take negative values, which is a signature of quantum behavior. When W(x,y) ≥ 0 for all points, the state admits a hidden variable description.

### Hidden Variable Model

The simulation works by:

1. Computing the Wigner function W(x,y) for each qutrit's initial state
2. Sampling phase space points (x,y) according to W as a probability distribution
3. Evolving these classical hidden variables through deterministic update rules for each gate
4. Computing measurement outcomes from the final (x,y) values

This approach efficiently simulates clifford quantum circuits as well as some circuits with wigner positive input states.

## Core Functions

### Phase Space Setup

**`single_qutrit_phase_space()`**

Constructs the phase point operators A(x,y) for a single qutrit. Returns a 3×3 array of operators.

### State Preparation

**`wigner_from_state(rho, A_ops, tol=1e-10)`**

Computes the discrete Wigner function for a given density matrix.

- **Parameters:**
  - `rho`: Density matrix (3×3 complex array)
  - `A_ops`: Phase point operators from `single_qutrit_phase_space()`
  - `tol`: Tolerance for zeroing small values
- **Returns:** 3×3 complex Wigner function

### Hidden Variable Sampling

**`sample_hidden_variables_batch(W_flat_list, num_runs)`**

Samples hidden variables for multiple qutrits across many runs.

- **Parameters:**
  - `W_flat_list`: List of flattened real Wigner functions (one per qutrit)
  - `num_runs`: Number of sampling runs
- **Returns:** Tuple (x, y) of shape (num_runs, n_qutrits)

### Quantum Gates

**Single-Qutrit Gates:**

- `Z_gate_batch(x, y, q)`: Phase gate on qutrit q
- `H_gate_batch(x, y, q)`: Hadamard-like gate on qutrit q

**Two-Qutrit Gates:**

- `CX_gate_batch(x, y, control_q, target_q)`: Controlled-X gate

All batch gates operate in-place on the hidden variable arrays x and y.

### Measurement

**`precompute_measurement_probs(A_ops)`**

Precomputes measurement probabilities for all phase space points. Call this once before batch measurements for efficiency.

**`Measure_batch(x, y, meas_probs)`**

Performs computational basis measurements on all qutrits.

- **Parameters:**
  - `x, y`: Hidden variable arrays (num_runs, n_qutrits)
  - `meas_probs`: Precomputed measurement probabilities
- **Returns:** Measurement outcomes (num_runs, n_qutrits)

### Visualization

**`plot_joint_probs(outcomes, d=3, plot_type='bar', title=None)`**

Visualizes joint measurement probability distributions.

- **Parameters:**
  - `outcomes`: Measurement outcomes array
  - `d`: Local dimension (3 for qutrits)
  - `plot_type`: 'bar', 'topk', or 'heatmap' (heatmap only for 2 qutrits)
  - `title`: Optional plot title

## Advanced Usage

### Creating Entangled States

```python
# Create a maximally entangled state
rho_0 = np.outer([1,0,0], [1,0,0])
W_list = [wigner_from_state(rho_0, A_ops) for _ in range(2)]
W_flat_list = [np.real(W).flatten() for W in W_list]

x, y = sample_hidden_variables_batch(W_flat_list, num_runs=10000)

# Create entanglement
H_gate_batch(x, y, 0)
CX_gate_batch(x, y, 0, 1)

# Measure and verify correlations
meas_probs = precompute_measurement_probs(A_ops)
outcomes = Measure_batch(x, y, meas_probs)

# Should show correlations between qutrit 0 and qutrit 1
plot_joint_probs(outcomes, plot_type='heatmap')
```

### Custom Initial States

```python
# Prepare superposition state |+⟩ = (|0⟩ + |1⟩ + |2⟩)/√3
plus_state = np.ones(3) / np.sqrt(3)
rho_plus = np.outer(plus_state, plus_state.conj())
W_plus = wigner_from_state(rho_plus, A_ops)

# Check if state has non-negative Wigner function
print("Minimum Wigner value:", np.min(np.real(W_plus)))
```

## Performance Considerations

- Use batch operations (`*_batch` functions) for simulating multiple runs
- Precompute measurement probabilities with `precompute_measurement_probs()` before measuring
- For large circuits, consider the memory requirements: O(num_runs × n_qutrits)
- The simulator is most efficient when Wigner functions remain non-negative (classical regime)

## Limitations

- Currently supports only single-qutrit and two-qutrit gates
- Measurements are only in the computational basis
- The simulator is exact only when all Wigner functions remain non-negative
- Memory scales linearly with number of sampling runs

## Mathematical Details

The phase space is defined using the discrete Heisenberg-Weyl group with:

```python
ω = exp(2πi/3)  # Cube root of unity
X = shift operator (cyclic permutation)
Z = clock operator (diagonal phase)
```

The displacement operators T(i,j) = ω^(-ij/2) Z^i X^j generate the phase point operators through:

```python
A(x,y) = T(x,y) A(0,0) T(x,y)†
```

where A(0,0) is the unique central reference operator.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## Citation

If you use this simulator in your research, please cite:

```bibtex
@software{wigner_simulator,
  title={Qutrit Wigner Simulator},
  author={Rishi_Goel},
  year={2025},
  url={https://github.com/rishigoel2003/wigner_simulator}
}
```

## References

- Wootters, W. K. "A Wigner-function formulation of finite-state quantum mechanics." Annals of Physics 176.1 (1987): 1-21.
- Gross, D. "Hudson's theorem for finite-dimensional quantum systems." Journal of Mathematical Physics 47.12 (2006): 122107.
- Veitch, V., et al. "Negative quasi-probability as a resource for quantum computation." New Journal of Physics 14.11 (2012): 113011.

## Contact

For questions and support, please open an issue on GitHub or contact [rishigoel25@gmail.com].