Skip to Content
DocsUnitaryLab Algorithms User ManualState Preparation Algorithms

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).

AlgorithmClassMethod Concept
MöttönenMottonenAlgorithmRecursive decomposition of uniformly controlled rotations with amplitude (RY) / phase (RZ) separation
MultiplexerMultiplexerAlgorithmProbability binary tree multiplexed RY rotations + diagonal phase gates
MPSMPSAlgorithmSequential SVD decomposition based on Matrix Product States (MPS) + QR unitary completion
PauliPauliAlgorithmFixed Pauli word ansatz + L-BFGS-B numerical optimization (the only non-exact analytical method)
SuperpositionSuperpositionAlgorithmSparse 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]
ParameterTypeDefaultDescription
Psilist / np.ndarrayRequiredTarget state amplitude vector
target_qubitsintRequired (UI default 2, max 8, min 1)Number of target qubits
target_errorfloat1e-6Target fidelity error threshold; the effective threshold used is max(target_error, 1e-10)
backend/device/dtype'torch'/'cpu'/np.complex128Reserved 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 Psi is 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 StatePreparationResult and a nested class 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]
ParameterTypeDefaultDescription
Psilist / np.ndarrayRequiredTarget state amplitude vector
target_qubitsintRequired (UI default 2, max 8, min 1)Number of target qubits
target_errorfloat1e-6Same error threshold and floor rule as Möttönen
backend/device/dtype'torch'/'cpu'/np.complex128Reserved 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 Psi is not specified, test() defaults to using the normalized [1, 1j, 0, 1].
  • For degenerate subtrees (left_norm + right_norm <= 1e-15), theta is directly set to 0.0 to 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]
ParameterTypeDefaultDescription
Psilist / np.ndarrayRequiredTarget state amplitude vector
target_qubitsintRequired (UI default 2, max 8, min 1)Number of target qubits
target_errorfloatCode default 1e-6 (parameters.json declares 1e-9; the two are inconsistent)Target error threshold
mpslist[np.ndarray] | NoneNoneOptional: directly pass an already-constructed list of MPS tensors; when None, it is automatically constructed from Psi
work_wireslist[int] | NoneNoneOptional: auxiliary work qubit indices; automatically derived when None
right_canonicalizeboolFalseWhether 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_dimint | NoneNoneOptional: limit the maximum bond dimension
rng_seedint42Internal random number seed
backend/device/dtype'torch'/'cpu'/np.complex128Reserved parameters; the current version uses the default execution configuration

mps, work_wires, right_canonicalize, mps_max_bond_dim, and rng_seed do not appear in parameters.json — the web UI parameter panel can only configure Psi/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 via self.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]
ParameterTypeDefaultDescription
Psilist / np.ndarrayRequiredTarget state amplitude vector
target_qubitsintRequired (UI default 2, max 6, min 1 — lower than the 8 used by the other 4 algorithms)Number of target qubits
target_errorfloat1e-6Target error threshold; because this is numerical optimization, reaching it is not guaranteed
backend/device/dtype'torch'/'cpu'/np.complex128Reserved 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 to target_qubits=1 (the test() of the other 4 algorithms defaults to 2, and Superposition defaults to 3), and this is inconsistent with the UI default value 2 in parameters.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 seed np.random.default_rng(7)), with at most 800 iterations per restart (_MAX_OPTIMIZATION_ITERATIONS = 800); once a given restart reaches target_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 uses functools.lru_cache to 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 SuperpositionAlgorithm

The Superposition directory 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]
ParameterTypeDefaultDescription
Psilist / np.ndarrayRequiredTarget state amplitude vector (can be sparse)
target_qubitsintRequired (UI default 2, max 8, min 1)Number of target qubits
target_errorfloatCode 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.complex128Reserved 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_matrix contains a del target_error statement with a comment noting that “QR completion is exact” — that is, although this internal helper function accepts target_error, it does not actually use it (total_error is 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 is max(target_qubits, work_wire + 1).

General Notes

  • The .run() methods of all 5 algorithms require Psi and target_qubits to be explicitly passed (no default values); only target_error and the remaining parameters have default values. Calling Algorithm().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_success criterion for all algorithms is total_error <= max(target_error, 1e-10); passing an excessively small target_error will not further improve the precision of this criterion.
  • backend/device/dtype are reserved parameters for compatibility; the current version uses the default execution configuration.
Last updated on