State Preparation Algorithms
Overview
The unitarylab_algorithms.state_preparation package provides 5 circuit construction methods for encoding an arbitrary (or specially structured) classical vector into the amplitudes of a quantum state. The 5 submodules are located at mottonen/, multiplexer/, mps/, pauli/, and Superposition/ (note that the last directory name is capitalized).
| Algorithm | Class | Method Concept |
|---|---|---|
| Möttönen | MottonenAlgorithm | Recursive decomposition of uniformly controlled rotations with amplitude (RY) / phase (RZ) separation |
| Multiplexer | MultiplexerAlgorithm | Probability binary tree multiplexed RY rotations + diagonal phase gates |
| MPS | MPSAlgorithm | Sequential SVD decomposition based on Matrix Product States (MPS) + QR unitary completion |
| Pauli | PauliAlgorithm | Fixed Pauli word ansatz + L-BFGS-B numerical optimization (the only non-exact analytical method) |
| Superposition | SuperpositionAlgorithm | Sparse support-set coefficient encoding + permutation circuit, exploiting the number of nonzero amplitudes |
All 5 algorithms use a unified return structure containing status, circuit_path, plot, circuit, and algorithm-specific results; plot points to the saved .txt result report. Because the Pauli method incurs higher numerical optimization overhead, the UI supports at most 6 target qubits; the other methods support up to 8. The advanced structural parameters of MPS can only be configured through the Python API.
Möttönen State Preparation
Background
Implements the state preparation algorithm proposed by Möttönen et al.: it separates the synthesis of amplitude (RY) and phase (RZ), recursively computing uniformly controlled rotation angles, then decomposes each uniformly controlled rotation along a Gray code ladder into an alternating sequence of single-qubit rotations and CNOTs (in order). If the target state is fully real (all phases ~0, tolerance 1e-15), the RZ uniformly controlled rotation stage is skipped entirely.
Import
from unitarylab_algorithms import MottonenAlgorithm.run() Parameters
def run(self, Psi, target_qubits: int, target_error: float = 1e-6,
backend='torch', device='cpu', dtype=np.complex128) -> Dict[str, Any]| Parameter | Type | Default | Description |
|---|---|---|---|
Psi | list / np.ndarray | Required | Target state amplitude vector |
target_qubits | int | Required (UI default 2, max 8, min 1) | Number of target qubits |
target_error | float | 1e-6 | Target fidelity error threshold; the effective threshold used is max(target_error, 1e-10) |
backend/device/dtype | — | 'torch'/'cpu'/np.complex128 | Reserved parameters; the current version uses the default execution configuration |
Return Value
{
"status": "ok",
"circuit_path": "<mottonen_state_preparation_algorithm_circuit.svg path>",
"plot": [{"format": "txt", "filename": "<mottonen_state_preparation_algorithm_result.txt>"}],
"circuit": <Circuit object>,
"Prepared state": ...,
"Total error": ...,
"Computation time (s)": ...,
}Example
from unitarylab_algorithms import MottonenAlgorithm
algo = MottonenAlgorithm()
result = algo.run(Psi=[1, 0, 0, 1], target_qubits=2, target_error=1e-6)
print(result['status'], result['Total error'])Quick Demo
from unitarylab_algorithms.state_preparation.mottonen.algorithm import test
test(Psi=[1, 0, 0, 1], target_qubits=2, target_error=1e-6)Notes
- When
Psiis not specified,test()defaults to using the Bell state[1, 0, 0, 1]/√2. - Internally, the actual computation is performed via
@dataclass(slots=True) class StatePreparationResultand a nestedclass Mottonen(StatePreparationResult);run()is just a thin wrapper.
Multiplexer State Preparation
Background
Implements multiplexed / uniformly controlled RY rotation state preparation: it recursively splits the amplitude using a probability binary tree (each node emits a possibly multi-controlled RY rotation, , routing the probability mass to the left and right subtrees), followed by an additional diagonal phase stage (selectively applying P/CP/MCP gates to each basis state with a nonzero phase).
Import
from unitarylab_algorithms import MultiplexerAlgorithm.run() Parameters
def run(self, Psi, target_qubits: int, target_error: float = 1e-6,
backend='torch', device='cpu', dtype=np.complex128) -> Dict[str, Any]| Parameter | Type | Default | Description |
|---|---|---|---|
Psi | list / np.ndarray | Required | Target state amplitude vector |
target_qubits | int | Required (UI default 2, max 8, min 1) | Number of target qubits |
target_error | float | 1e-6 | Same error threshold and floor rule as Möttönen |
backend/device/dtype | — | 'torch'/'cpu'/np.complex128 | Reserved parameters; the current version uses the default execution configuration |
Return Value
{
"status": "ok",
"circuit_path": "<multiplexer_state_preparation_algorithm_circuit.svg path>",
"plot": [{"format": "txt", "filename": "<multiplexer_state_preparation_algorithm_result.txt>"}],
"circuit": <Circuit object>,
"Prepared state": ...,
"Total error": ...,
"Computation time (s)": ...,
}Example
from unitarylab_algorithms import MultiplexerAlgorithm
algo = MultiplexerAlgorithm()
result = algo.run(Psi=[1, 1j, 0, 1], target_qubits=2, target_error=1e-6)
print(result['status'], result['Total error'])Quick Demo
from unitarylab_algorithms.state_preparation.multiplexer.algorithm import test
test(Psi=[1, 1j, 0, 1], target_qubits=2, target_error=1e-6)Notes
- When
Psiis not specified,test()defaults to using the normalized[1, 1j, 0, 1]. - For degenerate subtrees (
left_norm + right_norm <= 1e-15),thetais directly set to0.0to avoid division by zero.
MPS State Preparation
Background
Implements quantum state preparation based on Matrix Product States (MPS): the target state is first (optionally automatically) decomposed into an MPS via sequential SVD (see arXiv:2310.18410); each MPS tensor, as an isometry, is completed into a full unitary matrix via QR decomposition (Eq. 23); these unitary matrices act in turn on a system qubit and a shared set of auxiliary “work” qubits (encoding the bond indices); the final system state is extracted from the all-zero subspace of the work qubits.
Import
from unitarylab_algorithms import MPSAlgorithm.run() Parameters
def run(self, Psi, target_qubits: int, target_error: float = 1e-6,
mps: Optional[list[np.ndarray]] = None,
work_wires: Optional[list[int]] = None,
right_canonicalize: bool = False,
mps_max_bond_dim: Optional[int] = None,
rng_seed: int = 42,
backend='torch', device='cpu', dtype=np.complex128) -> Dict[str, Any]| Parameter | Type | Default | Description |
|---|---|---|---|
Psi | list / np.ndarray | Required | Target state amplitude vector |
target_qubits | int | Required (UI default 2, max 8, min 1) | Number of target qubits |
target_error | float | Code default 1e-6 (parameters.json declares 1e-9; the two are inconsistent) | Target error threshold |
mps | list[np.ndarray] | None | None | Optional: directly pass an already-constructed list of MPS tensors; when None, it is automatically constructed from Psi |
work_wires | list[int] | None | None | Optional: auxiliary work qubit indices; automatically derived when None |
right_canonicalize | bool | False | Whether to right-canonicalize; when mps=None (automatically constructed from Psi), this is silently overridden to False and the user-supplied value is ignored (because the automatically constructed MPS is already guaranteed to be right-canonical) |
mps_max_bond_dim | int | None | None | Optional: limit the maximum bond dimension |
rng_seed | int | 42 | Internal random number seed |
backend/device/dtype | — | 'torch'/'cpu'/np.complex128 | Reserved parameters; the current version uses the default execution configuration |
mps,work_wires,right_canonicalize,mps_max_bond_dim, andrng_seeddo not appear inparameters.json— the web UI parameter panel can only configurePsi/target_qubits/target_error; these 5 parameters can only be used via direct Python calls.
Return Value
{
"status": "ok",
"circuit_path": "<mps_state_preparation_algorithm_circuit.svg path>",
"plot": [{"format": "txt", "filename": "<mps_state_preparation_algorithm_result.txt>"}],
"circuit": <Circuit object>,
"Prepared state": ...,
"Total error": ...,
"Work leakage": ...,
"MPS tensors": ...,
"Computation time (s)": ...,
}Example
from unitarylab_algorithms import MPSAlgorithm
algo = MPSAlgorithm()
result = algo.run(Psi=[1, 0, 0, 1], target_qubits=2, target_error=1e-6)
print(result['status'], result['Work leakage'], result['MPS tensors'])Quick Demo
from unitarylab_algorithms.state_preparation.mps.algorithm import test
test(Psi=[1, 0, 0, 1], target_qubits=2, target_error=1e-6)Notes
- When
target_qubits == 0, a special fast path is taken, directly returning a trivial 0-qubit circuit and identity matrix, skipping the entire decomposition process. - It is the only one of the 5 algorithms that exposes the internal MPS structure (read-only attributes such as
unitaries,mps,work_leakage,full_evolution_result), but these attributes do not appear directly in the.run()return dictionary themselves, unless indirectly reflected through fields merged viaself.output.
Pauli State Preparation
Background
Implements a variational Pauli rotation (“Pauli word”) state preparation ansatz, explicitly mimicking PennyLane’s ArbitraryStatePreparation template: a fixed, recursively generated ordered list of Pauli strings (length ) in which each Pauli word corresponds to a trainable rotation angle; a sequence of rotation blocks acts on , optimized via L-BFGS-B (analytic parameter-shift gradients, multi-start restarts) to maximize fidelity with the target state. It is the only one of the 5 algorithms that is non-exact / analytical and relies on numerical optimization, and it does not guarantee reaching target_error for every target state (this limitation is explicitly stated in the README).
Import
from unitarylab_algorithms import PauliAlgorithm.run() Parameters
def run(self, Psi, target_qubits: int, target_error: float = 1e-6,
backend='torch', device='cpu', dtype=np.complex128) -> Dict[str, Any]| Parameter | Type | Default | Description |
|---|---|---|---|
Psi | list / np.ndarray | Required | Target state amplitude vector |
target_qubits | int | Required (UI default 2, max 6, min 1 — lower than the 8 used by the other 4 algorithms) | Number of target qubits |
target_error | float | 1e-6 | Target error threshold; because this is numerical optimization, reaching it is not guaranteed |
backend/device/dtype | — | 'torch'/'cpu'/np.complex128 | Reserved parameters; the current version uses the default execution configuration |
Return Value
{
"status": "ok",
"circuit_path": "<pauli_state_preparation_algorithm_circuit.svg path>",
"plot": [{"format": "txt", "filename": "<pauli_state_preparation_algorithm_result.txt>"}],
"circuit": <Circuit object>,
"Prepared state": ...,
"Total error": ...,
"Pauli words": ...,
"Weights": ...,
"Computation time (s)": ...,
}Example
from unitarylab_algorithms import PauliAlgorithm
algo = PauliAlgorithm()
result = algo.run(Psi=[1, 1j], target_qubits=1, target_error=1e-6)
print(result['status'], result['Total error'], result['Pauli words'])Quick Demo
from unitarylab_algorithms.state_preparation.pauli.algorithm import test
test(Psi=[1, 1j], target_qubits=1, target_error=1e-6)Notes
test()defaults totarget_qubits=1(thetest()of the other 4 algorithms defaults to2, and Superposition defaults to3), and this is inconsistent with the UI default value2inparameters.json— this is a difference in the example’s default value, not in the default value of the.run()signature itself.- Multi-start optimization restarts at most 4 times (
_MAX_OPTIMIZATION_RESTARTS = 4, fixed seednp.random.default_rng(7)), with at most 800 iterations per restart (_MAX_OPTIMIZATION_ITERATIONS = 800); once a given restart reachestarget_error, it breaks out of the restart loop early, so it is not guaranteed to actually run the full 4 restarts. - Depends on the external module
unitarylab.library.pauli_operator.pauli_string_decomposition, and usesfunctools.lru_cacheto cache the dense Pauli matrices for different qubit counts (not cleared for the lifetime of the process).
Superposition State Preparation
Background
Implements a sparse support-set “superposition state” preparation method (the code comments state it is “inspired by the high-level idea of PennyLane’s Superposition template,” but is an independent rewrite): for a target state with only nonzero amplitudes, it first constructs a compact -term coefficient state on as small a register as possible via QR completion, then applies an explicit permutation circuit (using 1 work qubit in a “mark—CNOT—unmark” pattern) to map these compact indices onto the real (arbitrarily distributed) sparse support basis states, thereby avoiding the overhead of general dense-state synthesis.
Import
from unitarylab_algorithms import SuperpositionAlgorithmThe
Superpositiondirectory name in the import path is capitalized, unlike the other 4 lowercase directory names (mottonen/multiplexer/mps/pauli) — take care with capitalization when writing it.
.run() Parameters
def run(self, Psi, target_qubits: int, target_error: float = 1e-6,
backend='torch', device='cpu', dtype=np.complex128) -> Dict[str, Any]| Parameter | Type | Default | Description |
|---|---|---|---|
Psi | list / np.ndarray | Required | Target state amplitude vector (can be sparse) |
target_qubits | int | Required (UI default 2, max 8, min 1) | Number of target qubits |
target_error | float | Code default 1e-6 (parameters.json declares 1e-9; the two are inconsistent, the same issue pattern as MPS) | Target error threshold |
backend/device/dtype | — | 'torch'/'cpu'/np.complex128 | Reserved parameters; the current version uses the default execution configuration |
Return Value
{
"status": "ok",
"circuit_path": "<superposition_state_preparation_algorithm_circuit.svg path>",
"plot": [{"format": "txt", "filename": "<superposition_state_preparation_algorithm_result.txt>"}],
"circuit": <Circuit object>,
"Prepared state": ...,
"Total error": ...,
"Support size": ...,
"Index register qubits": ...,
"Computation time (s)": ...,
}Example
import numpy as np
from unitarylab_algorithms import SuperpositionAlgorithm
algo = SuperpositionAlgorithm()
Psi = np.ones(8, dtype=complex) / np.sqrt(8)
result = algo.run(Psi=Psi, target_qubits=3, target_error=1e-6)
print(result['status'], result['Support size'], result['Index register qubits'])Quick Demo
from unitarylab_algorithms.state_preparation.Superposition.algorithm import test
test(target_qubits=3, Psi=None, target_error=1e-6)Notes
test()defaults to using a 3-qubit sparse state (nonzero amplitudes at indices 1 and 6,Psi[1]=1/√2,Psi[6]=1j/√2).- Internally,
_build_coefficient_stage_matrixcontains adel target_errorstatement with a comment noting that “QR completion is exact” — that is, although this internal helper function acceptstarget_error, it does not actually use it (total_erroris still computed downstream from the comparison between the final circuit and the target state, and is unaffected by this). - When
register_qubits == 0(support set size ≤ 1), the coefficient stage matrix directly returns the identity matrix, skipping QR completion. - Requires 1 additional work qubit (default
work_wire = target_qubits); the total circuit width ismax(target_qubits, work_wire + 1).
General Notes
- The
.run()methods of all 5 algorithms requirePsiandtarget_qubitsto be explicitly passed (no default values); onlytarget_errorand the remaining parameters have default values. CallingAlgorithm().run()directly with no arguments will raise an error — be sure to refer to each algorithm’s example. - The 5 algorithms share the same “uniformly controlled rotation / MPS / Pauli ansatz / sparse superposition” family of ideas, covering different application scenarios ranging from general dense states (Möttönen, Multiplexer), structured low-entanglement states (MPS), and variational approximation (Pauli), to sparse states (Superposition); the choice should be based on the sparsity and entanglement structure of the target state and the required precision.
- The
is_successcriterion for all algorithms istotal_error <= max(target_error, 1e-10); passing an excessively smalltarget_errorwill not further improve the precision of this criterion. backend/device/dtypeare reserved parameters for compatibility; the current version uses the default execution configuration.