Metadata-Version: 2.4
Name: unitarylab
Version: 1.3.1
Summary: A Python package for quantum simulator from UnitaryLab.
Author: UnitaryLab
License-Expression: LicenseRef-UnitaryLab-LICENSE
Requires-Python: <3.13,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.zh-CN
License-File: LICENSE.en
Requires-Dist: numpy
Requires-Dist: scipy
Requires-Dist: torch
Requires-Dist: matplotlib
Requires-Dist: pylatexenc
Requires-Dist: scikit-learn
Requires-Dist: sympy
Requires-Dist: mpmath
Requires-Dist: cqlib
Requires-Dist: networkx
Dynamic: license-file

<div align="center">

<h1>unitarylab</h1>

<p>
  <strong>A Python quantum circuit simulator for building, executing, analyzing, and exporting quantum circuits.</strong><br/>
  <strong>面向量子电路构建、执行、分析与导出的 Python 量子模拟器。</strong>
</p>

<p>
  <img src="https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12-3b82f6?style=flat-square&logo=python&logoColor=white" alt="Python 3.10, 3.11, and 3.12"/>
  <img src="https://img.shields.io/badge/Backend-NumPy%20%7C%20PyTorch%20%7C%20C%2B%2B%20%7C%20TensorNet-7c3aed?style=flat-square" alt="NumPy, PyTorch, C++, and TensorNet backends"/>
  <img src="https://img.shields.io/badge/Interface-Circuit-f59e0b?style=flat-square" alt="Circuit interface"/>
  <img src="https://img.shields.io/badge/License-UnitaryLab-22c55e?style=flat-square" alt="UnitaryLab license"/>
</p>

<p>
  <a href="#english">English</a>
  &middot;
  <a href="#chinese">中文</a>
</p>

</div>

---

<a id="english"></a>

# English

## Introduction

`unitarylab` provides a high-level `Circuit` interface, statevector and
matrix-product-state execution, circuit drawing and analysis, OpenQASM
interoperability, transpilation, and a collection of quantum algorithms.

The top-level user API is:

```python
from unitarylab import Circuit, Register, ClassicalRegister
```

The simulator uses little-endian qubit ordering when displaying basis-state
labels. For example, basis-state strings are interpreted with qubit 0 at the
right-hand end of the underlying statevector indexing convention.

Minimal example: statevector index `1` is labeled `"01"`, so `qubit 0 = 1`
and `qubit 1 = 0`.

## Installation

```bash
pip install unitarylab
```

Verify the installation:

```python
import unitarylab

print(unitarylab.__version__)
```

The source uses Python 3.10+ syntax. NumPy is a core dependency. PyTorch powers
the default execution backend; SciPy is used by TensorNet and algorithm
features; Matplotlib is required for Matplotlib circuit drawing. The C++
backend requires the packaged native `cppgates` extension, and GPU execution
requires a compatible PyTorch environment.

For a CPU-only PyTorch installation:

```bash
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install unitarylab
```

## Quick Start

Create and execute a Bell-state circuit:

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute()

print(result.state)
print(result.probabilities)
```

The probability distribution is approximately:

```python
{"00": 0.5, "11": 0.5}
```

Common gate families include single-qubit gates, rotation gates, controlled
and multi-controlled gates, SWAP, and custom unitary matrices.

## Measurement and Sampling

Create a classical register and map measured qubits to classical bits:

```python
from unitarylab import Circuit, Register, ClassicalRegister

q = Register("q", 2)
c = ClassicalRegister("c", 2)
circuit = Circuit(q, c)

circuit.h(q[0])
circuit.cx(q[0], q[1])
circuit.measure(q[0:2], c[0:2])

result = circuit.execute(shots=1000, seed=42)

print(result.counts)
print(result.classical_results_map)
print(result.classical_registers)
```

`shots` must be a positive integer and defaults to `1`. `seed` defaults to
`42`; pass `None` for nondeterministic circuit measurements.

- `counts` aggregates measured classical bit strings over all shots.
- `classical_results_map` contains the final shot as `{classical_bit: value}`.
- `classical_registers` groups the final-shot values by register name.
- Unmeasured classical bits are represented by `#` in count keys and by `-1`
  in register snapshots.

You can also sample an already executed quantum state without adding circuit
measurement instructions:

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute()
samples = result.sample(shots=10, qubits=[0, 1], seed=7)
print(samples)
```

`sample()` does not collapse the stored result state. In contrast,
`result.measure(...)` performs a projective measurement and updates the state.

## Execution Result

`Circuit.execute()` returns an `ExecutionResult` or, for TensorNet execution,
a compatible `TensorNetExecutionResult`.

| Member | Purpose |
|---|---|
| `state` | Dense statevector. TensorNet materializes it on access. |
| `backend_state` | Native backend representation, such as an MPS. |
| `probabilities` | Full basis-state probability mapping. |
| `probability(bitstring, qubits=None)` | Probability of one outcome. |
| `marginal_probabilities(qubits=None)` | Distribution on selected qubits. |
| `sample(shots, qubits=None, seed=None)` | Sample without collapsing the state. |
| `expectation(observable, qubits=None)` | Normalized observable expectation value. |
| `measure(qubits, seed=None)` | Measure and collapse selected qubits. |
| `counts` | Classical counts collected by `execute(shots=...)`. |
| `classical_registers` | Final classical values grouped by register. |

Example probability queries:

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
result = circuit.execute(backend="numpy")

print(result.probability("00"))
print(result.probability("0", qubits=[0]))
print(result.marginal_probabilities(qubits=[0]))
```

Computing every dense probability or requesting `.state` can be expensive for
large systems. Prefer targeted probability, marginal, sampling, or expectation
queries where possible.

## Observables and Expectation Values

Use `result.expectation(...)` with these supported user-facing forms:

- A full-length Pauli string, such as `"ZZ"` for a two-qubit result.
- A Pauli string plus explicit qubits, such as `"XX", qubits=(0, 2)`.
- A single-qubit 2×2 Hermitian matrix plus one qubit.
- A list of `(coefficient, observable)` or
  `(coefficient, observable, qubits)` tuples.
- A list of mappings with `coeff`, `pauli`, and optional `qubits` keys.

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
result = circuit.execute(backend="numpy")

print(result.expectation("ZZ"))
print(result.expectation("X", qubits=0))

hamiltonian = [
    (0.5, "ZZ", (0, 1)),
    {"coeff": -0.25, "pauli": "X", "qubits": (0,)},
]
print(result.expectation(hamiltonian))
```

Pauli labels are limited to `I`, `X`, `Y`, and `Z`. Matrix observables must be
finite, Hermitian, 2×2 matrices and currently apply to exactly one qubit.

## Execution Backends

`Circuit.execute()` accepts `initial_state`, `backend`, `device`, `dtype`,
`shots`, `seed`, and `backend_options`. Its defaults are `backend="torch"`,
`device="cpu"`, `dtype=np.complex128`, `shots=1`, and `seed=42`.

| Backend | Device | Native state | Main notes |
|---|---|---|---|
| `torch` | `cpu`, `gpu` | PyTorch tensor | GPU requires a compatible PyTorch environment. |
| `numpy` | `cpu` | NumPy array | Dense statevector execution. |
| `cpp` | `cpu` | NumPy-compatible result | Requires the native `cppgates` extension. |
| `tensornet` | `cpu` | `TensorNetState` MPS | Supports `max_bond`, `cutoff`, and routing options. |

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

torch_result = circuit.execute(backend="torch", device="cpu")
numpy_result = circuit.execute(backend="numpy", device="cpu")
# Requires the compiled C++ extension:
# cpp_result = circuit.execute(backend="cpp", device="cpu")
tensornet_result = circuit.execute(backend="tensornet", device="cpu")
```

`backend_options` is accepted only by the TensorNet backend. Torch GPU support
depends on the installed PyTorch backend. On Apple MPS, complex128 is rejected
and the implementation limits execution to at most 16 qubits.

## Density-Matrix Simulation

Use `Circuit.execute_density()` when the full mixed-state density matrix is
required. This is a separate CPU backend backed by the native `cppdensity`
extension; it is not selected through the `backend` argument of
`Circuit.execute()`.

```python
import numpy as np

from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute_density(
    initial_state=None,
    device="cpu",
    dtype=np.complex128,
)

print(result.state)          # shape: (2**n, 2**n)
print(result.trace)          # approximately 1
print(result.purity)         # 1 for this pure Bell state
print(result.probabilities)  # {"00": 0.5, "11": 0.5}
print(result.expectation("ZZ"))
```

`initial_state` may be `None`, a statevector, a density matrix, or a
`DensityMatrixState`. `complex64` and `complex128` are supported. The returned
`DensityMatrixResult` provides `state`, `backend_state`, `trace`, `purity`,
`probabilities`, `probability()`, `marginal_probabilities()`, and
`expectation()`.

The density-matrix backend currently supports CPU unitary evolution and does
not support circuit measurement, state collapse, shots/counts, or GPU
execution.

### Noise Models

The density-matrix backend supports five built-in single-qubit channels:
`BitFlip`, `PhaseFlip`, `DepolarizingNoise`, `AmplitudeDamping`, and
`PhaseDamping`. Bind channels to circuit operations with `NoiseOperator`, add
the rules to a `NoiseModel`, and pass the model to `execute_density()`.

```python
from unitarylab import Circuit
from unitarylab.backend import (
    AmplitudeDamping,
    BitFlip,
    NoiseModel,
    NoiseOperator,
)

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.rx(0.3, 1)

noise_model = NoiseModel([
    # Apply bit flip after matching X/RX gates on their target qubits.
    NoiseOperator(BitFlip(0.02), gate_ids=("x", "rx")),
    # Apply amplitude damping only when qubit 1 is a gate target.
    NoiseOperator(AmplitudeDamping(0.05), qubits=(1,)),
])

result = circuit.execute_density(noise_model=noise_model)
print(result.state)
print(result.trace)
print(result.purity)
```

`NoiseOperator(channel, gate_ids=None, qubits=None)` has these V1 semantics:

- `gate_ids=None` matches every physical primitive gate; otherwise only the
  listed gate identifiers match. Configured identifiers are normalized to
  lowercase.
- `qubits=None` applies the channel independently to every target of the
  matched gate. A qubit tuple filters `gate.target` only; controls do not match.
- Noise is inserted after the matched gate. Multiple rules execute in
  `NoiseModel` insertion order.
- A multi-target gate receives independent single-qubit channels in target
  order. This is not correlated multi-qubit noise.
- Identity and global-phase operations do not trigger noise.

Depolarizing noise uses
`E(rho) = (1-p) rho + p I/2`; `p=1` produces the maximally mixed single-qubit
state. Phase damping multiplies off-diagonal coherence by `1-gamma`.
The underlying Kraus representation is internal. User-defined Kraus channels,
before-gate noise, correlated multi-qubit noise, readout error, and GPU noise
are not currently exposed.

## Circuit Operations

Frequently used circuit operations include:

| Operation | Behavior |
|---|---|
| `initialize(state, qubits)` | Append state-preparation gates. |
| `append(other, target, ...)` | Mutate the circuit by appending a circuit block. |
| `prepend(other, target, ...)` | Mutate the circuit by prepending a circuit block. |
| `inverse()` | Return a new inverse circuit. |
| `dagger()` | Return a new Hermitian-adjoint circuit. |
| `repeat(times)` | Return a new repeated circuit. |
| `control(num_control_qubits, ...)` | Return a new controlled circuit. |
| `decompose(n=1, name=None)` | Return a new circuit with block gates decomposed. |
| `transpile(gates_to_unroll=None, basis="default")` | Return a transpiled circuit. |

`append()` and `prepend()` modify the receiving circuit. Transformations such
as `inverse()`, `dagger()`, `repeat()`, `control()`, and `decompose()` return new
circuits and leave the source circuit unchanged.

```python
from unitarylab import Circuit

block = Circuit(2)
block.h(0)
block.cx(0, 1)

circuit = Circuit(2)
circuit.append(block, target=[0, 1])

inverse = circuit.inverse()
repeated = circuit.repeat(2)
controlled = circuit.control(1)
decomposed = circuit.decompose()
transpiled = circuit.transpile()
```

`initialize()` accepts a normalized statevector whose dimension matches the
selected qubits.

## TensorNet

The TensorNet backend stores states as an open-boundary matrix product state
(MPS). Use `TensorNetState` to create, copy, or convert user-level MPS states.

```python
import numpy as np

from unitarylab import Circuit
from unitarylab.backend.tensornet import TensorNetState

dense_state = np.array([1, 0, 0, 0], dtype=np.complex128)
mps_state = TensorNetState.from_statevector(dense_state, max_bond=64)

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute(
    initial_state=mps_state,
    backend="tensornet",
    backend_options={
        "max_bond": 64,
        "cutoff": 1e-10,
        "routing": "auto",
    },
)

print(result.backend_state)
print(result.backend_state.to_statevector())
```

Each MPS tensor uses `(left bond, physical dimension 2, right bond)` ordering;
the first tensor represents qubit 0. `routing` may be `"auto"`, `"swap"`, or
`"mpo"`.

Explicit `max_bond` and `cutoff` values in `backend_options` override values
stored in the input `TensorNetState`. The input is copied before execution.
Statevector backends do not implicitly contract MPS inputs; convert explicitly
with `to_statevector()`.

## Drawing and Analysis

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

circuit.draw()
print(circuit.draw(output="text"))
circuit.draw(output="latex", filename="bell.tex")
circuit.draw(filename="bell.png", title="Bell State")

info = circuit.analyze(show=False)
print(info.depth())
print(info.count_ops())
info.show()

matrix = circuit.get_matrix(backend="numpy")
```

Drawing outputs support Matplotlib (`mpl`/`matplotlib`), text (`text`/`txt`),
and LaTeX (`latex`/`tex`/`quantikz`).

## Transpiler

Use `Circuit.transpile()` to unroll gates into a supported basis and apply the
configured transpilation pipeline:

```python
from unitarylab import Circuit

circuit = Circuit(3)
circuit.h(0)
circuit.mcx([0, 1], 2)

transpiled = circuit.transpile(basis="default")
print(transpiled.draw(output="text"))
```

For direct access to the same implementation, use
`QCompiler.transpile(circuit, ...)`.

## Compilation and Hardware Submission

The `Circuit` methods cover the common optimization, transpilation, mapping,
and hardware-submission flow:

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

optimized = circuit.optimize(optimization_level=1)
transpiled = circuit.transpile(basis="default")

# Optimize, transpile, and map without submitting a hardware job.
compiled = circuit.compile(provider="guodun")

# Compile and submit a real hardware job.
result = circuit.submit(
    provider="guodun",
    token=TOKEN,
    shots=1024,
)
```

`optimize()` and `transpile()` return `Circuit` objects. `compile()` returns a
`CompilationResult` containing the mapped circuit and executable QCIS; it does
not submit a job and consumes no task credits. Real-machine providers still
require login credentials, because mapping logs in to download the live chip
calibration: provide them through the ``GUODUN_LOGIN_KEY`` environment variable
or ``hardware.login_key`` in a ``QCompiler`` config (``Circuit.compile`` has no
``token`` argument). `submit()` returns a provider-neutral `HardwareResult`; with mitigation
enabled through a ``QCompiler`` config (``execution.mitigation.method``),
`result.counts` and `result.probabilities` return the mitigated observations.
``Circuit.submit`` runs without mitigation by default, in which case `counts`
holds the raw counts and `probabilities` is empty.

Use `QCompiler` for OpenQASM input or detailed optimization, synthesis,
mapping, execution, or mitigation configuration. Its pipeline methods accept
OpenQASM or `Circuit`; direct `transpile()` accepts `Circuit`:

```python
from unitarylab import QCompiler

compiler = QCompiler(config)
optimized = compiler.optimize(circuit_or_qasm)
transpiled = compiler.transpile(circuit, basis="default")
compiled = compiler.compile(circuit_or_qasm, provider="guodun")
result = compiler.submit(
    circuit_or_qasm,
    provider="guodun",
    token=TOKEN,
    shots=1024,
)
```

Advanced artifact-level control remains available through `QCompiler.run()`:

```python
run = compiler.run(
    qcis,
    start_from="mapped_qcis",
    stop_at="hardware_result",
)
```

The underlying artifact flow is `raw_qasm -> optimized_qasm -> mapped_qcis ->
hardware_result`. Guodun/Tianyan and the other supported providers are routed
internally by `QCompiler.submit()`; callers do not choose between the pipeline
and hardware adapters themselves. The non-Guodun adapters require their vendor
SDKs to be installed separately: LQCloud needs the ``lqcloud`` SDK and QPanda3
needs ``pyqpanda3`` (Quafu needs no separate SDK); a missing SDK is reported at
submission time.

### Low-level Mapping API

When layout details or a custom submission flow are needed, the mapping module
can be called directly (the same interface as the repository's
``qcompiler.mapping``, only the import path differs):

```python
from unitarylab.qcompiler.mapping import map_qasm, submit, plot_probabilities

qcis, layout = map_qasm(qasm_text, token=TOKEN)   # QASM -> mapped QCIS + final layout
result = submit(qcis, token=TOKEN, shots=2000, layout=layout)  # real submission, decoded to logical order
plot_probabilities(result, title="Bell State")
```

Note: this ``submit`` returns a plain dict (``probability`` /
``raw_probability`` / ``lab_id`` / ``exp_id`` / ``query_id``), unlike
``Circuit.submit``'s ``HardwareResult``; ``map_qasm`` hard-codes the target
machine ``gd_qc1``, requires a token, and has no offline cache.

## Serialization and OpenQASM

The circuit API supports OpenQASM 2.0 and 3.0, file import/export, and Python
source generation:

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.rx(0.5, 0)
circuit.cx(0, 1)

qasm3 = circuit.to_qasm()
qasm2 = circuit.to_qasm2()
restored = Circuit.from_qasm(qasm3)

circuit.to_qasm_file("bell.qasm")
from_file = Circuit.from_qasm_file("bell.qasm")

python_source = circuit.to_python(variable_name="bell")
circuit.to_python_file("bell.py", variable_name="bell")
```

`from_qasm()` auto-detects OpenQASM 2.0 or 3.0 from the header. `to_qasm()`
and `to_qasm_file()` emit OpenQASM 3.0; `to_qasm2()` emits OpenQASM 2.0.
Some gates must be decomposed or transpiled before QASM export. Python source
generation currently supports native `rx`, `ry`, `rz`, `p`, and `cx` gates;
unsupported or nested gate structures raise `RuntimeError`.

## Algorithm Library

The following user-facing areas are available below `unitarylab.library`.
Chemistry APIs are intentionally outside the scope of this quick guide.

| Area | Main entry points |
|---|---|
| Quantum Fourier transform | `QFT`, `IQFT` |
| Quantum phase estimation | `QPE` |
| Linear combination of unitaries | `LCU` |
| Block encoding | `block_encode`, with FABLE and Nagy subpackages |
| Hamiltonian simulation | `hamiltonian_simulation`, QSP/Trotter/Taylor/QDrift methods |
| Signal and singular-value transformation | `QSP`, `QSVT` |
| Linear systems | `solve`, plus HHL/QSVT/Schrödingerization/AQC/VQLS/CKS solvers |
| Equations | Differential operators, parsing, and Schrödingerization APIs |
| Fermi-Hubbard | Hamiltonian construction, ground-state, and magnetic-moment utilities |
| Pauli operators | Decomposition, evolution, products, and matrix conversion |

Example:

```python
from unitarylab import Circuit
from unitarylab.library import QFT

qft = QFT(n=4)
circuit = Circuit(4)
circuit.append(qft, target=[0, 1, 2, 3])
print(circuit.draw(output="text"))
```

## FAQ and Troubleshooting

**Why does the C++ backend fail to import?**  
The native `cppgates` extension must be included and compatible with the active
Python environment. Use the NumPy or Torch CPU backend when it is unavailable.

**Why does Torch GPU execution reject complex128?**  
The Apple MPS path explicitly rejects complex128. Use complex64 or CPU. Other
GPU behavior depends on the installed PyTorch backend.

**Why is TensorNet `.state` expensive?**  
Accessing `.state` contracts the MPS into a dense vector. Prefer probability,
sampling, expectation, or `backend_state` operations for larger systems.

**Why does QASM export raise `NotImplementedError`?**  
The circuit contains an operation not directly representable in the target
QASM format. Try `to_qasm(decompose=True, transpile=True)` when the operation
can be lowered to supported gates.

**Are basis labels little-endian?**  
Yes. Keep the simulator's little-endian display convention in mind when
interpreting multi-qubit states, probabilities, measurements, and samples.

## Further Documentation

- [UnitaryLab website](https://unitarylab.com/)
- [Simulator user manual](https://docs.unitarylab.com/en/docs/unitarylab-simulator-user-manual/)
- [UnitaryLab Algorithms on PyPI](https://pypi.org/project/unitarylab-algorithms/)

---

<a id="chinese"></a>

# 中文

## 简介

`unitarylab` 以高层 `Circuit` 接口为核心，支持状态向量和矩阵乘积态模拟、
电路绘图与分析、OpenQASM 互操作、转译及常用量子算法。

顶层用户入口为：

```python
from unitarylab import Circuit, Register, ClassicalRegister
```

模拟器显示计算基标签时采用 little-endian 量子比特顺序。最小示例：状态向量
索引 `1` 的概率标签为 `"01"`，即 `qubit 0 = 1`、`qubit 1 = 0`。

## 安装

```bash
pip install unitarylab
```

验证安装：

```python
import unitarylab

print(unitarylab.__version__)
```

源码使用 Python 3.10+ 语法。NumPy 是核心依赖；PyTorch 提供默认执行后端；
SciPy 用于 TensorNet 和部分算法能力；Matplotlib 用于 Matplotlib 电路绘图。
C++ 后端要求安装包包含可用的 `cppgates` 原生扩展，GPU 执行要求兼容的
PyTorch 环境。

## 快速开始

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute()
print(result.state)
print(result.probabilities)
```

概率分布约为 `{"00": 0.5, "11": 0.5}`。

## 测量与采样

```python
from unitarylab import Circuit, Register, ClassicalRegister

q = Register("q", 2)
c = ClassicalRegister("c", 2)
circuit = Circuit(q, c)

circuit.h(q[0])
circuit.cx(q[0], q[1])
circuit.measure(q[0:2], c[0:2])

result = circuit.execute(shots=1000, seed=42)
print(result.counts)
print(result.classical_results_map)
print(result.classical_registers)
```

`shots` 必须是正整数，默认值为 `1`；`seed` 默认值为 `42`，传入 `None`
可使用非确定性电路测量。

- `counts` 汇总所有 shots 的经典比特字符串。
- `classical_results_map` 保存最后一次 shot 的 `{经典位: 测量值}`。
- `classical_registers` 按寄存器名称组织最后一次测量值。
- 未测量经典位在 counts 键中表示为 `#`，在寄存器快照中表示为 `-1`。

执行后也可以直接采样量子态：

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
result = circuit.execute()

print(result.sample(shots=10, qubits=[0, 1], seed=7))
```

`sample()` 不会坍缩结果中的状态；`result.measure(...)` 会执行投影测量并更新状态。

## 执行结果

| 成员 | 用途 |
|---|---|
| `state` | 稠密状态向量；TensorNet 在访问时进行收缩。 |
| `backend_state` | 后端原生状态，例如 MPS。 |
| `probabilities` | 完整计算基概率映射。 |
| `probability(bitstring, qubits=None)` | 查询单个结果概率。 |
| `marginal_probabilities(qubits=None)` | 查询指定量子比特的边缘分布。 |
| `sample(shots, qubits=None, seed=None)` | 不坍缩状态的采样。 |
| `expectation(observable, qubits=None)` | 计算归一化期望值。 |
| `measure(qubits, seed=None)` | 测量并坍缩指定量子比特。 |
| `counts` | `execute(shots=...)` 收集的经典计数。 |
| `classical_registers` | 按经典寄存器分组的最终值。 |

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
result = circuit.execute(backend="numpy")

print(result.probability("00"))
print(result.probability("0", qubits=[0]))
print(result.marginal_probabilities(qubits=[0]))
```

## Observable 与期望值

`result.expectation(...)` 支持：完整 Pauli 字符串；Pauli 字符串加指定量子比特；
单量子比特 2×2 Hermitian 矩阵；tuple 项或 mapping 项组成的列表。

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
result = circuit.execute(backend="numpy")

print(result.expectation("ZZ"))
print(result.expectation("X", qubits=0))

hamiltonian = [
    (0.5, "ZZ", (0, 1)),
    {"coeff": -0.25, "pauli": "X", "qubits": (0,)},
]
print(result.expectation(hamiltonian))
```

Pauli 标签仅支持 `I/X/Y/Z`。矩阵 Observable 必须是有限、Hermitian 的 2×2
矩阵，并且当前只能作用于一个量子比特。

## 执行后端

`execute()` 默认使用 `backend="torch"`、`device="cpu"`、
`dtype=np.complex128`、`shots=1`、`seed=42`。

| 后端 | device | 原生状态 | 主要限制 |
|---|---|---|---|
| `torch` | `cpu`、`gpu` | PyTorch tensor | GPU 依赖 PyTorch 环境。 |
| `numpy` | `cpu` | NumPy array | 稠密状态向量。 |
| `cpp` | `cpu` | NumPy 兼容结果 | 需要 `cppgates` 原生扩展。 |
| `tensornet` | `cpu` | `TensorNetState` MPS | 支持截断和 routing 配置。 |

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

torch_result = circuit.execute(backend="torch", device="cpu")
numpy_result = circuit.execute(backend="numpy", device="cpu")
# 需要已编译的 C++ 扩展：
# cpp_result = circuit.execute(backend="cpp", device="cpu")
tensornet_result = circuit.execute(backend="tensornet", device="cpu")
```

`backend_options` 仅适用于 TensorNet。Apple MPS 路径不支持 complex128，
且源码将执行规模限制为最多 16 个量子比特。

## 密度矩阵模拟

需要完整混态密度矩阵时，可使用 `Circuit.execute_density()`。它是由原生
`cppdensity` 扩展支持的独立 CPU 后端，不通过 `Circuit.execute()` 的
`backend` 参数选择。

```python
import numpy as np

from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute_density(
    initial_state=None,
    device="cpu",
    dtype=np.complex128,
)

print(result.state)          # shape 为 (2**n, 2**n)
print(result.trace)          # 约等于 1
print(result.purity)         # 当前 Bell 纯态为 1
print(result.probabilities)  # {"00": 0.5, "11": 0.5}
print(result.expectation("ZZ"))
```

`initial_state` 可以是 `None`、状态向量、密度矩阵或 `DensityMatrixState`，
支持 `complex64` 和 `complex128`。返回的 `DensityMatrixResult` 提供
`state`、`backend_state`、`trace`、`purity`、`probabilities`、
`probability()`、`marginal_probabilities()` 和 `expectation()`。

当前密度矩阵后端支持 CPU unitary 演化，暂不支持电路测量、状态坍缩、
shots/counts 和 GPU 执行。

### 噪声模型

当前提供五种内置单量子比特 channel：`BitFlip`、`PhaseFlip`、
`DepolarizingNoise`、`AmplitudeDamping` 和 `PhaseDamping`。使用
`NoiseOperator` 将 channel 与门匹配规则绑定，加入 `NoiseModel` 后传给
`execute_density()`。

```python
from unitarylab import Circuit
from unitarylab.backend import (
    AmplitudeDamping,
    BitFlip,
    NoiseModel,
    NoiseOperator,
)

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.rx(0.3, 1)

noise_model = NoiseModel([
    # 在匹配的 X/RX 门后，对门的 target 添加 bit flip。
    NoiseOperator(BitFlip(0.02), gate_ids=("x", "rx")),
    # 只有当 qubit 1 是门的 target 时才添加 amplitude damping。
    NoiseOperator(AmplitudeDamping(0.05), qubits=(1,)),
])

result = circuit.execute_density(noise_model=noise_model)
print(result.state)
print(result.trace)
print(result.purity)
```

`NoiseOperator(channel, gate_ids=None, qubits=None)` 的 V1 语义如下：

- `gate_ids=None` 匹配所有实体 primitive gate；否则只匹配列出的 gate id，
  配置值会统一转换为小写。
- `qubits=None` 对匹配门的每个 target 独立应用 channel；指定 qubit tuple
  时只过滤 `gate.target`，不匹配 control。
- noise 固定插入到匹配门之后；多条规则严格按照加入 `NoiseModel` 的顺序执行。
- 多 target 门按照 target 顺序应用独立单比特 channel，不表示相关多比特噪声。
- identity 和 global phase 不触发 noise。

Depolarizing noise 使用 `E(rho) = (1-p) rho + p I/2`，因此 `p=1` 时得到
单比特完全混合态。Phase damping 将非对角 coherence 乘以 `1-gamma`。
底层 Kraus 表示目前是内部接口；暂不公开用户自定义 Kraus channel、门前噪声、
相关多比特噪声、readout error 和 GPU noise。

## 常用电路操作

| 操作 | 行为 |
|---|---|
| `initialize(state, qubits)` | 追加状态制备门。 |
| `append(other, target, ...)` | 原地追加电路块。 |
| `prepend(other, target, ...)` | 原地前置电路块。 |
| `inverse()` / `dagger()` | 返回新的逆电路或共轭转置电路。 |
| `repeat(times)` | 返回新的重复电路。 |
| `control(n, ...)` | 返回新的受控电路。 |
| `decompose()` | 返回新的分解电路。 |
| `transpile()` | 返回转译后的电路。 |

```python
from unitarylab import Circuit

block = Circuit(2)
block.h(0)
block.cx(0, 1)

circuit = Circuit(2)
circuit.append(block, target=[0, 1])

inverse = circuit.inverse()
repeated = circuit.repeat(2)
controlled = circuit.control(1)
decomposed = circuit.decompose()
transpiled = circuit.transpile()
```

`append()` 和 `prepend()` 修改接收电路；其余表中列出的变换返回新电路。
`initialize()` 要求输入归一化状态向量，维度与目标量子比特数量匹配。

## TensorNet

TensorNet 后端以开放边界矩阵乘积态（MPS）保存状态。`TensorNetState` 是用户级
MPS 状态入口。

```python
import numpy as np

from unitarylab import Circuit
from unitarylab.backend.tensornet import TensorNetState

dense_state = np.array([1, 0, 0, 0], dtype=np.complex128)
mps_state = TensorNetState.from_statevector(dense_state, max_bond=64)

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

result = circuit.execute(
    initial_state=mps_state,
    backend="tensornet",
    backend_options={
        "max_bond": 64,
        "cutoff": 1e-10,
        "routing": "auto",
    },
)

print(result.backend_state)
print(result.backend_state.to_statevector())
```

MPS 张量维度顺序为 `(左键合, 物理维度 2, 右键合)`，第一个张量对应 qubit 0。
`routing` 支持 `auto/swap/mpo`。显式 `max_bond` 和 `cutoff` 会覆盖输入状态中
的配置。状态向量后端不会隐式收缩 MPS，应先调用 `to_statevector()`。

## 绘图与分析

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

circuit.draw()
print(circuit.draw(output="text"))
circuit.draw(output="latex", filename="bell.tex")
circuit.draw(filename="bell.png", title="Bell State")

info = circuit.analyze(show=False)
print(info.depth())
print(info.count_ops())
info.show()

matrix = circuit.get_matrix(backend="numpy")
```

绘图支持 Matplotlib、文本和 LaTeX 输出。

## Transpiler

```python
from unitarylab import Circuit

circuit = Circuit(3)
circuit.h(0)
circuit.mcx([0, 1], 2)

transpiled = circuit.transpile(basis="default")
print(transpiled.draw(output="text"))
```

如需直接使用同一底层实现，可调用
`QCompiler.transpile(circuit, ...)`。

## 编译与硬件提交

`Circuit` 接口覆盖常用的优化、门展开、mapping 和硬件提交流程：

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.h(0)
circuit.cx(0, 1)

optimized = circuit.optimize(optimization_level=1)
transpiled = circuit.transpile(basis="default")

# 优化、门展开和 mapping，但不提交硬件任务
compiled = circuit.compile(provider="guodun")

# 编译并提交真实硬件任务
result = circuit.submit(
    provider="guodun",
    token=TOKEN,
    shots=1024,
)
```

`optimize()` 和 `transpile()` 返回 `Circuit`。`compile()` 返回包含映射后
线路和可执行 QCIS 的 `CompilationResult`；它不提交任务、不消耗任务积分，
但真机 provider 仍需登录凭据，因为映射阶段要登录平台并下载实时芯片校准：
通过环境变量 `GUODUN_LOGIN_KEY` 或 `QCompiler` 配置中的 `hardware.login_key`
提供（`Circuit.compile` 没有 `token` 参数）。`submit()` 统一返回
`HardwareResult`；开启缓解时（`QCompiler` 配置 `execution.mitigation.method`）
`result.counts` 和 `result.probabilities` 返回缓解后结果。`Circuit.submit`
默认无缓解，此时 `counts` 为原始计数而 `probabilities` 为空。

如果输入是 OpenQASM，或需要详细配置优化、综合、mapping、execution
和 mitigation，可直接使用 `QCompiler`。其 pipeline 方法支持 OpenQASM
或 `Circuit`，直接调用 `transpile()` 时输入为 `Circuit`：

```python
from unitarylab import QCompiler

compiler = QCompiler(config)
optimized = compiler.optimize(circuit_or_qasm)
transpiled = compiler.transpile(circuit, basis="default")
compiled = compiler.compile(circuit_or_qasm, provider="guodun")
result = compiler.submit(
    circuit_or_qasm,
    provider="guodun",
    token=TOKEN,
    shots=1024,
)
```

artifact 级高级控制仍由 `QCompiler.run()` 提供：

```python
run = compiler.run(
    qcis,
    start_from="mapped_qcis",
    stop_at="hardware_result",
)
```

底层产物流程为 `raw_qasm -> optimized_qasm -> mapped_qcis ->
hardware_result`。Guodun/Tianyan 与其他支持的厂商均由
`QCompiler.submit()` 在内部分流，用户无需自行选择 pipeline 或 hardware adapter。
非 Guodun 的适配器需要单独安装厂商 SDK：LQCloud 需要 ``lqcloud`` SDK，
QPanda3 需要 ``pyqpanda3``（Quafu 无需额外 SDK）；SDK 缺失会在提交时报错。

### 底层 Mapping API

需要布局细节或自定义提交流程时，可单独调用 mapping 模块（与仓库
`qcompiler.mapping` 同名接口，仅 import 路径不同）：

```python
from unitarylab.qcompiler.mapping import map_qasm, submit, plot_probabilities

qcis, layout = map_qasm(qasm_text, token=TOKEN)   # QASM -> 映射后 QCIS + 最终布局
result = submit(qcis, token=TOKEN, shots=2000, layout=layout)  # 真机提交，按布局换算逻辑位序
plot_probabilities(result, title="Bell State")
```

注意：此处的 `submit` 返回 dict（`probability`/`raw_probability`/`lab_id`/
`exp_id`/`query_id`），与 `Circuit.submit` 的 `HardwareResult` 不同；
`map_qasm` 硬编码目标机器 `gd_qc1`、要求 token 且无离线缓存。

## 序列化与 OpenQASM

```python
from unitarylab import Circuit

circuit = Circuit(2)
circuit.rx(0.5, 0)
circuit.cx(0, 1)

qasm3 = circuit.to_qasm()
qasm2 = circuit.to_qasm2()
restored = Circuit.from_qasm(qasm3)

circuit.to_qasm_file("bell.qasm")
from_file = Circuit.from_qasm_file("bell.qasm")

python_source = circuit.to_python(variable_name="bell")
circuit.to_python_file("bell.py", variable_name="bell")
```

`from_qasm()` 根据头部自动识别 OpenQASM 2.0 或 3.0；`to_qasm()` 和
`to_qasm_file()` 输出 3.0，`to_qasm2()` 输出 2.0。部分量子门必须先分解或
转译才能导出。Python 源码生成当前支持原生 `rx/ry/rz/p/cx` 门；不支持或
嵌套的门结构会抛出 `RuntimeError`。

## 算法库概览

以下用户级能力位于 `unitarylab.library`。本快速指南不介绍 Chemistry。

| 方向 | 主要入口 |
|---|---|
| 量子傅里叶变换 | `QFT`、`IQFT` |
| 量子相位估计 | `QPE` |
| 线性组合酉算子 | `LCU` |
| 块编码 | `block_encode`，以及 FABLE、Nagy 子包 |
| 哈密顿量模拟 | `hamiltonian_simulation` 及 QSP/Trotter/Taylor/QDrift 方法 |
| 信号与奇异值变换 | `QSP`、`QSVT` |
| 线性方程组 | `solve` 及 HHL/QSVT/Schrödinger化/AQC/VQLS/CKS 求解器 |
| 方程 | 微分算子、方程解析和 Schrödingerization |
| Fermi-Hubbard | Hamiltonian 构建、基态与磁矩工具 |
| Pauli Operator | 分解、演化、乘积和矩阵转换 |

```python
from unitarylab import Circuit
from unitarylab.library import QFT

qft = QFT(n=4)
circuit = Circuit(4)
circuit.append(qft, target=[0, 1, 2, 3])
print(circuit.draw(output="text"))
```

## FAQ 与故障排查

**C++ 后端导入失败？**  
确认当前 Python 环境包含兼容的 `cppgates` 原生扩展；不可用时使用 NumPy
或 Torch CPU 后端。

**TensorNet 的 `.state` 为什么开销较大？**  
访问 `.state` 会把 MPS 收缩成稠密向量。较大系统应优先使用概率、采样、
期望值查询或直接访问 `backend_state`。

**QASM 导出为什么抛出 `NotImplementedError`？**  
电路包含目标 QASM 无法直接表示的操作。在能够降级为受支持门的情况下，尝试
`to_qasm(decompose=True, transpile=True)`。

**计算基标签是否为 little-endian？**  
是。解释多量子比特状态、概率、测量和采样时应考虑这一显示约定。

## 更多文档

- [UnitaryLab 官网](https://unitarylab.com/)
- [Simulator 用户手册](https://docs.unitarylab.com/zh/docs/unitarylab-simulator-user-manual/)
- [UnitaryLab Algorithms（PyPI）](https://pypi.org/project/unitarylab-algorithms/)

---

# License

License: LicenseRef-UnitaryLab-LICENSE. 
The Chinese license text is authoritative; the English version is provided for reference only.
