# Quickstart Guide

**Time to complete: 20-30 minutes**

This guide walks you through generating and executing your first quantum gate
pulse with QubitOS. We start from physics — the Hamiltonian that governs
your qubit — and show how QubitOS optimizes pulse shapes to implement any
desired unitary evolution.

## What You'll Learn

1. How QubitOS uses Hamiltonians to control qubits
2. How to define a system Hamiltonian with Pauli strings
3. How to optimize a pulse using GRAPE
4. How to use gate presets as shortcuts
5. How to execute pulses and interpret results

## Prerequisites

- QubitOS installed ([Installation Guide](installation.txt))
- Basic Python knowledge
- Basic understanding of quantum mechanics

---

## 1. What is QubitOS?

QubitOS is a **Hamiltonian-level quantum control system**. It works at the
physics layer: you specify the Hamiltonian of your system, the target unitary
evolution you want, and QubitOS optimizes the electromagnetic control pulses
to achieve it.

### The Core Idea

Every quantum gate is a unitary evolution generated by a Hamiltonian:

$$
U = \mathcal{T}\exp\left(-i\int_0^T \left[H_0 + \sum_k u_k(t) H_k\right] dt\right)
$$

where:
- $H_0$ is your **drift Hamiltonian** (always-on qubit frequencies, couplings)
- $H_k$ are **control Hamiltonians** (how your drive lines couple to qubits)
- $u_k(t)$ are the **control amplitudes** — what QubitOS optimizes

Standard gates like X, Y, Z, CNOT are just specific target unitaries.
QubitOS treats them as convenient presets, not fundamental primitives.

```
QubitOS Workflow:
    Hamiltonian + Target Unitary → GRAPE Optimizer → Calibrated Pulse → Execution
         ↑                              ↑
    Your physics                  Pulse engineering
```

---

## 2. Defining Your System Hamiltonian

QubitOS uses **Pauli string notation** to define Hamiltonians concisely.

### Pauli String Basics

```python
from qubitos.pulsegen.hamiltonians import build_hamiltonian

# A transmon qubit with 5 GHz frequency:
#   H_0 = (ω/2) σ_z = 2π × 5.0 × (σ_z / 2)
# Controlled by X and Y drives:
#   H_c = [σ_x, σ_y]

H0, Hc = build_hamiltonian(
    drift="5.0 * Z0",        # Drift: 5 GHz qubit frequency (Z on qubit 0)
    controls=["X0", "Y0"],   # Control: X and Y drive on qubit 0
    num_qubits=1,
)

print(f"Drift shape: {H0.shape}")        # (2, 2)
print(f"Controls: {len(Hc)} operators")   # 2 operators
```

### Multi-Qubit Systems

```python
# Two coupled transmons:
#   H_0 = 5.0 Z₀ + 5.2 Z₁ + 0.02 Z₀Z₁ (ZZ coupling)
#   H_c = [X₀, Y₀, X₁, Y₁]

H0, Hc = build_hamiltonian(
    drift="5.0 * Z0 + 5.2 * Z1 + 0.02 * Z0Z1",
    controls=["X0", "Y0", "X1", "Y1"],
    num_qubits=2,
)

print(f"Drift shape: {H0.shape}")        # (4, 4)
print(f"Controls: {len(Hc)} operators")   # 4 operators
```

---

## 3. Optimizing Your First Pulse

Let's optimize a pulse to implement an X gate (quantum NOT) on a single qubit.

### The Hamiltonian-First Way

```python
import numpy as np
from qubitos.pulsegen import GrapeOptimizer, GrapeConfig
from qubitos.pulsegen.hamiltonians import build_hamiltonian, get_target_unitary

# Step 1: Define the physics
H0, Hc = build_hamiltonian(
    drift="5.0 * Z0",        # Qubit frequency
    controls=["X0", "Y0"],   # Drive channels
    num_qubits=1,
)

# Step 2: Define the target evolution
# X gate: U = exp(-i π/2 σ_x) = [[0, 1], [1, 0]]  (up to global phase)
target = get_target_unitary("X", num_qubits=1)

# Step 3: Configure GRAPE optimization
config = GrapeConfig(
    num_time_steps=100,      # Pulse discretization (100 samples)
    duration_ns=50,          # Total gate time: 50 nanoseconds
    max_iterations=200,      # Maximum optimization steps
    target_fidelity=0.999,   # Stop when fidelity ≥ 99.9%
    learning_rate=0.1,       # Gradient ascent step size
)

# Step 4: Optimize
optimizer = GrapeOptimizer(config)
result = optimizer.optimize(target, num_qubits=1)

print(f"Converged: {result.converged}")
print(f"Fidelity:  {result.fidelity:.6f}")
print(f"Iterations: {result.iterations}")
print(f"I envelope: {result.i_envelope.shape}")  # (100,)
print(f"Q envelope: {result.q_envelope.shape}")  # (100,)
```

**Why start with the Hamiltonian?** Because it's the physics. When you move
to real hardware, the drift Hamiltonian changes (different qubit frequency,
different coupling strength), and you re-optimize against the *actual* physics
of your device.

### Gate Presets: The Shortcut

For standard gates, QubitOS provides presets that wrap the Hamiltonian setup:

```python
# This does the same thing as above, with default Hamiltonians
result = optimizer.optimize(
    get_target_unitary("X", num_qubits=1),
    num_qubits=1,
)
```

Gate presets are convenience wrappers — they construct a default Hamiltonian
and target unitary for you. Available presets:

| Gate | Matrix | Hamiltonian evolution |
|------|--------|---------------------|
| `X`  | $\begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}$ | $e^{-i\pi\sigma_x/2}$ |
| `Y`  | $\begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix}$ | $e^{-i\pi\sigma_y/2}$ |
| `Z`  | $\begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}$ | $e^{-i\pi\sigma_z/2}$ |
| `H`  | $\frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}$ | $(e^{-i\pi\sigma_x/2})(e^{-i\pi\sigma_z/4})$ |

---

## 4. Understanding the Pulse Output

The GRAPE optimizer returns two **envelope arrays** that define the control
pulse:

$$
\Omega(t) = u_I(t)\cos(\omega_d t) + u_Q(t)\sin(\omega_d t)
$$

- **I envelope** (`result.i_envelope`): In-phase control amplitudes $u_I(t)$
- **Q envelope** (`result.q_envelope`): Quadrature control amplitudes $u_Q(t)$
- **Fidelity**: $F = \frac{1}{d^2}|\text{Tr}(U_{\text{target}}^\dagger U_{\text{achieved}})|^2$

### Visualizing

```python
import matplotlib.pyplot as plt

t = np.linspace(0, 50, len(result.i_envelope))

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 5), sharex=True)
ax1.plot(t, result.i_envelope, 'b-', lw=1.5)
ax1.set_ylabel('I Amplitude')
ax1.set_title('GRAPE-Optimized X-Gate Pulse')
ax1.grid(True, alpha=0.3)

ax2.plot(t, result.q_envelope, 'r-', lw=1.5)
ax2.set_ylabel('Q Amplitude')
ax2.set_xlabel('Time (ns)')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
```

---

## 5. Checking the Error Budget

Before executing on hardware, check whether the pulse fits within your
decoherence error budget:

```python
from qubitos.error_budget import ErrorBudget

budget = ErrorBudget(
    target_fidelity=0.99,
    t1_us={0: 50.0},   # T1 = 50 μs for qubit 0
    t2_us={0: 30.0},    # T2 = 30 μs for qubit 0
)
budget.add_gate(
    infidelity=1 - result.fidelity,
    qubit=0,
    duration_ns=50,
    label="X gate",
)

print(f"Projected fidelity: {budget.projected_fidelity:.4f}")
print(f"Within budget: {budget.can_append(infidelity=0.001, qubit=0, duration_ns=20)}")
```

---

## 6. Executing on a Backend

### Using the CLI

```bash
# Generate a pulse
qubit-os pulse generate --gate X --duration 50 --fidelity 0.999

# Execute on the QuTiP simulator
qubit-os pulse execute x_gate.json --shots 1024
```

### Using Python

```python
from qubitos.client import HALClientSync

with HALClientSync("localhost:50051") as client:
    execution = client.execute_pulse(
        i_envelope=result.i_envelope.tolist(),
        q_envelope=result.q_envelope.tolist(),
        duration_ns=50,
        target_qubits=[0],
        num_shots=1024,
    )

    print(f"Bitstring counts: {execution.bitstring_counts}")
    # Expected: ~99% in |1⟩ (X gate flips |0⟩ → |1⟩)
```

---

## 7. CLI Reference

| Command | Description |
|---------|-------------|
| `qubit-os hal health` | Check HAL server status |
| `qubit-os hal backends` | List available backends |
| `qubit-os pulse generate` | Generate optimized pulse |
| `qubit-os pulse execute` | Execute a pulse |
| `qubit-os --help` | Show all commands |

---

## Next Steps

You've learned the core QubitOS workflow:
**Hamiltonian → Target Unitary → GRAPE → Pulse → Execution**

### Continue Learning

- **[Pulse Generation Tutorial](../tutorials/pulse-generation.txt)**: Pulse shapes and parametric gates
- **[GRAPE Optimizer Deep Dive](../tutorials/grape-optimizer.txt)**: Optimization strategies and convergence
- **[Custom Hamiltonians](../tutorials/custom-hamiltonians.txt)**: Multi-qubit systems and advanced Hamiltonians
- **[API Reference](../api/index.txt)**: Complete API documentation

### Exercises

1. **Different Hamiltonians**: Change the drift frequency and re-optimize. How does the pulse shape change?
2. **Two-qubit gate**: Build a 2-qubit Hamiltonian and optimize a CNOT gate
3. **Error budget**: Chain multiple gates and check whether the sequence fits in the T1/T2 budget
4. **Decoherence**: Use `qubitos.lindblad` to simulate open-system dynamics
