Matrix types upstream of :mod:qamomile.observable.
Exposes:
:class:
HermitianMatrix, which wraps a dense Hermitian NumPy matrix and produces an exact Pauli decomposition as a :class:qamomile.observable.Hamiltonianvia the Fast Walsh-Hadamard Transform.Sample-based partial-diagonalization helpers (:func:
subspace_hamiltonian, :func:generalized_subspace_matrices, :func:solve_subspace) that build Hamiltonian / overlap matrices on a subspace spanned by a set of computational-basis or mixed-Pauli-basis product states and (optionally) solve the resulting (generalised) eigenvalue problem.Möttönen amplitude-encoding angle precomputation (:func:
compute_mottonen_amplitude_encoding_ry_angles, :func:compute_mottonen_amplitude_encoding_rz_angles, :func:validate_and_normalize_amplitudes). The actual quantum gate emission lives under :mod:qamomile.circuit.algorithm.state_preparation; this module provides only the classical angle math so that hybrid loops can pre-compute angle vectors outside any kernel and feed them toamplitude_encoding_from_anglesviaparameters=[...].
Overview¶
| Function | Description |
|---|---|
compute_mottonen_amplitude_encoding_ry_angles | Pre-compute the flat Ry angle vector for a Möttönen encoding. |
compute_mottonen_amplitude_encoding_rz_angles | Pre-compute the flat Rz angle vector for the phase-restoration stage. |
generalized_subspace_matrices | Build (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases. |
solve_subspace | Solve the (generalized) subspace eigenvalue problem. |
subspace_hamiltonian | Build H_sub[i, j] = ⟨s_i| H |s_j⟩ for Z-basis bitstring samples. |
validate_and_normalize_amplitudes | Validate an amplitude vector and return its normalised form. |
| Class | Description |
|---|---|
HermitianMatrix | Dense Hermitian operator on n qubits (a 2**n x 2**n matrix). |
Functions¶
compute_mottonen_amplitude_encoding_ry_angles [source]¶
def compute_mottonen_amplitude_encoding_ry_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarrayPre-compute the flat Ry angle vector for a Möttönen encoding.
Returns the Gray-walk Ry angles for the magnitude stage of the
encoding. For real inputs (or complex inputs with zero imaginary
part) this corresponds to the single-stage signed-Ry encoding.
For complex inputs with non-zero imaginary part, these are the
magnitude-stage angles only — pair with
:func:compute_mottonen_amplitude_encoding_rz_angles to obtain
the phase-stage angles needed to reproduce the full complex
state.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector of length 2**n. Real or complex; it is normalised automatically. |
Returns:
np.ndarray — np.ndarray: 1-D array of length 2**n - 1 holding the
Gray-walk Ry angles laid out level by level.
Raises:
ValueError— If the input is not a 1-D vector, the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
compute_mottonen_amplitude_encoding_rz_angles [source]¶
def compute_mottonen_amplitude_encoding_rz_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarrayPre-compute the flat Rz angle vector for the phase-restoration stage.
Returns the Gray-walk Rz angles for the phase stage of the
Möttönen encoding. For real inputs (or complex inputs with zero
imaginary part) the phase stage is unnecessary and the returned
array is all zeros. For complex inputs with non-zero imaginary
part, the returned angles together with the Ry angles from
:func:compute_mottonen_amplitude_encoding_ry_angles reproduce
the full complex state.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector of length 2**n. Real or complex; it is normalised automatically. |
Returns:
np.ndarray — np.ndarray: 1-D array of length 2**n - 1 holding the
Gray-walk Rz angles laid out level by level (all zeros for
real inputs).
Raises:
ValueError— If the input is not a 1-D vector, the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
generalized_subspace_matrices [source]¶
def generalized_subspace_matrices(
samples: Sequence[Sequence[int]] | np.ndarray,
bases: Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
) -> tuple[np.ndarray, np.ndarray]Build (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases.
Each sample |s_i; β_i⟩ = ⊗_q |β_{i,q}, s_{i,q}⟩ is a tensor
product of single-qubit Pauli eigenstates with per-qubit basis
β_{i,q} and bit s_{i,q}. The returned matrices are
H_sub[i, j] = ⟨s_i; β_i| H |s_j; β_j⟩,
S_sub[i, j] = ⟨s_i; β_i| s_j; β_j⟩,so the variational eigenpairs are recovered by solving the
generalized eigenvalue problem H_sub v = λ S_sub v (see
:func:solve_subspace).
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of bitstrings, with samples[k][q] the eigenvalue index for qubit q in basis bases[k][q] (0 for the +1 eigenstate, 1 for the -1 eigenstate). |
bases | Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | Per-qubit basis labels. Either a 1-D sequence of length num_qubits (broadcast across all samples) or a 2-D (n_samples, num_qubits) array. Each entry is either a Pauli.X / Y / Z member or the corresponding integer code 0 / 1 / 2. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian whose matrix elements are taken. |
Returns:
np.ndarray — Tuple (H_sub, S_sub) of complex ndarray s with shape
np.ndarray — (n_samples, n_samples).
Raises:
ValueError— On shape, range, or label mismatches insamplesorbases.
Example:
Mixed-basis subspace with one Z-basis sample (|0⟩) and one
X-basis sample (|+⟩) for H = Z:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import generalized_subspace_matrices
>>> H = qm_o.Z(0)
>>> samples = [(0,), (0,)] # bit=0 in each basis
>>> bases = [(qm_o.Pauli.Z,), (qm_o.Pauli.X,)] # |0⟩ and |+⟩
>>> H_sub, S_sub = generalized_subspace_matrices(samples, bases, H)
>>> S_sub # off-diagonal ⟨0|+⟩ = 1/√2, not identity
array([[1. +0.j, 0.7071+0.j],
[0.7071+0.j, 1. +0.j]])
>>> H_sub # ⟨0|Z|0⟩=1, ⟨+|Z|+⟩=0, ⟨0|Z|+⟩=1/√2
array([[1. +0.j, 0.7071+0.j],
[0.7071+0.j, 0. +0.j]])solve_subspace [source]¶
def solve_subspace(
samples: Sequence[Sequence[int]] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
*,
bases: Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | None = None,
overlap_tol: float = 1e-12,
) -> tuple[np.ndarray, np.ndarray]Solve the (generalized) subspace eigenvalue problem.
When bases is None every sample is treated as a Z-basis
bitstring, S_sub is the identity (assuming distinct samples),
and a standard Hermitian eigendecomposition of
:func:subspace_hamiltonian is returned. Otherwise the generalised
eigenvalue problem H_sub v = λ S_sub v is reduced to a standard
Hermitian one by whitening S_sub via its eigendecomposition,
using overlap_tol as the cutoff on the overlap eigenvalues. The
same projection step automatically handles a rank-deficient
S_sub (duplicate or near-duplicate samples make the overlap
singular).
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of length-num_qubits bitstrings. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian to project. |
bases | Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | None | Optional per-qubit or per-sample basis labels (see :func:generalized_subspace_matrices). Defaults to None, which means every qubit of every sample is in the Z basis. |
overlap_tol | float | Threshold below which an eigenvalue of S_sub is treated as zero in the rank-deficient fallback. Must be non-negative. Defaults to 1e-12. |
Returns:
np.ndarray — Tuple (eigvals, eigvecs). eigvals is a real 1-D array
np.ndarray — sorted in ascending order; eigvecs[:, k] is the variational
tuple[np.ndarray, np.ndarray] — coefficient vector of the k-th eigenstate in the basis of
tuple[np.ndarray, np.ndarray] — the supplied samples. In the rank-deficient fallback path the
tuple[np.ndarray, np.ndarray] — number of returned eigenpairs is the effective rank of
tuple[np.ndarray, np.ndarray] — S_sub.
Raises:
ValueError— Ifoverlap_tolis negative, any input fails validation in :func:subspace_hamiltonian/ :func:generalized_subspace_matrices, or every overlap eigenvalue is belowoverlap_tol(the supplied basis spans no usable directions; loweroverlap_tolor pass a better-conditioned set of samples).
Example:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import solve_subspace
>>> H = -qm_o.X(0)
>>> # Sampling the |+⟩ eigenstate of X recovers the ground state.
>>> eigvals, _ = solve_subspace(
... [(0,)], H, bases=(qm_o.Pauli.X,)
... )
>>> float(eigvals[0])
-1.0subspace_hamiltonian [source]¶
def subspace_hamiltonian(
samples: Sequence[Sequence[int]] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
) -> np.ndarrayBuild H_sub[i, j] = ⟨s_i| H |s_j⟩ for Z-basis bitstring samples.
Vectorised over all (i, j) pairs using XOR masks to detect the
X/Y support of each Pauli term and Z/Y parities for the sign and
phase. Identical bitstrings produce a degenerate row/column; dedupe
upstream if the resulting subspace must be non-degenerate.
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of length-num_qubits bitstrings, with samples[k][q] the Z-eigenvalue index (0 for |0⟩, 1 for |1⟩) of qubit q. Excess columns are truncated. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian whose matrix elements are taken. hamiltonian.constant contributes c · I on the diagonal of the projected matrix. |
Returns:
np.ndarray — Complex ndarray of shape (n_samples, n_samples) holding
np.ndarray — ⟨s_i|H|s_j⟩.
Raises:
ValueError— Ifsampleshas the wrong rank, fewer columns thanhamiltonian.num_qubits, or out-of-range entries.
Example:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import subspace_hamiltonian
>>> H = qm_o.Z(0) + qm_o.X(1)
>>> # All four Z-basis bitstrings on 2 qubits
>>> samples = [(0, 0), (1, 0), (0, 1), (1, 1)]
>>> H_sub = subspace_hamiltonian(samples, H)
>>> H_sub.shape
(4, 4)validate_and_normalize_amplitudes [source]¶
def validate_and_normalize_amplitudes(
amplitudes: Sequence[float] | Sequence[complex] | np.ndarray,
) -> tuple[np.ndarray, int, bool]Validate an amplitude vector and return its normalised form.
The Möttönen construction (arXiv:quant-ph/0407010, Section III) works on a unit-norm amplitude vector indexed by computational basis states; this helper enforces that pre-condition so the downstream angle computation can assume a well-formed input.
Inputs may be real or complex. A complex input whose imaginary
part is identically zero (within np.allclose tolerance) is
coerced to a real array; this preserves the cheaper signed-RY fast
path for vectors that happen to arrive boxed as complex.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector. Must be a 1-D sequence whose length is a power of two and at least 2, with at least one non-zero entry. |
Returns:
tuple[np.ndarray, int, bool] — tuple[np.ndarray, int, bool]:
(normalized, num_qubits, is_complex) where
normalized is a unit-norm np.ndarray,
num_qubits is log2(len(amplitudes)), and
is_complex is True iff the input has a non-zero
imaginary component (and therefore needs the
phase-restoration stage downstream). When is_complex
is False, normalized.dtype is float; otherwise
it is complex.
Raises:
ValueError— If the input is not a 1-D vector (e.g., a nested sequence or a 2-Dnp.ndarray), the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
Classes¶
HermitianMatrix [source]¶
class HermitianMatrixDense Hermitian operator on n qubits (a 2**n x 2**n matrix).
Wraps a NumPy array and validates on construction that it is 2D square,
has a power-of-two dimension, and is Hermitian up to atol. The
Hermitian check can be skipped via validate=False for internal use
(for example, when the caller already knows a result is Hermitian by
construction).
The main entry point for downstream quantum code is
:meth:to_hamiltonian, which returns the exact Pauli decomposition as
a :class:qamomile.observable.Hamiltonian via the Fast Walsh-Hadamard
Transform in O(n * 4**n) time.
Example:
>>> import numpy as np
>>> from qamomile.linalg import HermitianMatrix
>>> X = np.array([[0, 1], [1, 0]], dtype=complex)
>>> Z = np.array([[1, 0], [0, -1]], dtype=complex)
>>> H = HermitianMatrix(np.kron(X, Z) + 0.5 * np.kron(Z, np.eye(2)))
>>> H.num_qubits
2
>>> ham = H.to_hamiltonian()Constructor¶
def __init__(
self,
matrix: np.ndarray,
*,
validate: bool = True,
atol: float = 1e-10,
) -> NoneAttributes¶
matrix: np.ndarray Return a read-only view of the underlying array.num_qubits: int Number of qubits (log2of the matrix dimension).shape: tuple[int, int] Shape of the underlying matrix as(2**n, 2**n).
Methods¶
to_hamiltonian¶
def to_hamiltonian(self, *, tol: float = 1e-12) -> HamiltonianReturn the exact Pauli decomposition as a Hamiltonian.
Uses FWHT internally. Pauli terms with |coeff| <= tol are
dropped. The all-identity component is accumulated into
:attr:qamomile.observable.Hamiltonian.constant. Qubit 0
corresponds to the least-significant bit of the matrix’s
computational-basis indices.
Parameters:
| Name | Type | Description |
|---|---|---|
tol | float | Absolute tolerance below which a Pauli coefficient is treated as zero and omitted from the result. |
Returns:
Hamiltonian — class:~qamomile.observable.Hamiltonian containing the
Hamiltonian — Pauli decomposition.
qamomile.linalg.hermitian¶
Dense Hermitian matrix wrapper with Pauli decomposition via FWHT.
Overview¶
| Function | Description |
|---|---|
fwht_pauli_coefficients | Compute real Pauli coefficients alpha[x_mask, z_mask] via FWHT. |
is_close_zero | Check if a given floating-point value is close to zero within a small tolerance. |
is_power_of_two | Check whether n is a positive power of two. |
| Class | Description |
|---|---|
HermitianMatrix | Dense Hermitian operator on n qubits (a 2**n x 2**n matrix). |
Functions¶
fwht_pauli_coefficients [source]¶
def fwht_pauli_coefficients(matrix: np.ndarray, *, imag_tol: float = 1e-10) -> tuple[np.ndarray, int]Compute real Pauli coefficients alpha[x_mask, z_mask] via FWHT.
Uses the symplectic indexing where qubit k is determined by bit
k of each mask::
(x_bit, z_bit) -> Pauli
(0, 0) -> I
(1, 0) -> X
(0, 1) -> Z
(1, 1) -> YQubit 0 corresponds to the least-significant bit of the matrix’s computational-basis indices.
Parameters:
| Name | Type | Description |
|---|---|---|
matrix | np.ndarray | Dense (N, N) array with N = 2**n. Assumed Hermitian. |
imag_tol | float | If any coefficient has |Im| > imag_tol a ValueError is raised, which acts as a late guard against non-Hermitian input. |
Returns:
np.ndarray — A pair (coeffs, num_qubits) where coeffs is an (N, N)
int — real float64 ndarray indexed by (x_mask, z_mask).
is_close_zero [source]¶
def is_close_zero(value: float, abs_tol: float = 1e-15) -> boolCheck if a given floating-point value is close to zero within a small tolerance.
Parameters:
| Name | Type | Description |
|---|---|---|
value | float | The floating-point value to check. |
abs_tol | float | Absolute tolerance passed to :func:math.isclose. Defaults to 1e-15. |
Returns:
bool — True if the value is close to zero, False otherwise.
is_power_of_two [source]¶
def is_power_of_two(n: int) -> boolCheck whether n is a positive power of two.
Parameters:
| Name | Type | Description |
|---|---|---|
n | int | Integer to test. |
Returns:
bool — True if n is a power of two, False otherwise.
Classes¶
HermitianMatrix [source]¶
class HermitianMatrixDense Hermitian operator on n qubits (a 2**n x 2**n matrix).
Wraps a NumPy array and validates on construction that it is 2D square,
has a power-of-two dimension, and is Hermitian up to atol. The
Hermitian check can be skipped via validate=False for internal use
(for example, when the caller already knows a result is Hermitian by
construction).
The main entry point for downstream quantum code is
:meth:to_hamiltonian, which returns the exact Pauli decomposition as
a :class:qamomile.observable.Hamiltonian via the Fast Walsh-Hadamard
Transform in O(n * 4**n) time.
Example:
>>> import numpy as np
>>> from qamomile.linalg import HermitianMatrix
>>> X = np.array([[0, 1], [1, 0]], dtype=complex)
>>> Z = np.array([[1, 0], [0, -1]], dtype=complex)
>>> H = HermitianMatrix(np.kron(X, Z) + 0.5 * np.kron(Z, np.eye(2)))
>>> H.num_qubits
2
>>> ham = H.to_hamiltonian()Constructor¶
def __init__(
self,
matrix: np.ndarray,
*,
validate: bool = True,
atol: float = 1e-10,
) -> NoneAttributes¶
matrix: np.ndarray Return a read-only view of the underlying array.num_qubits: int Number of qubits (log2of the matrix dimension).shape: tuple[int, int] Shape of the underlying matrix as(2**n, 2**n).
Methods¶
to_hamiltonian¶
def to_hamiltonian(self, *, tol: float = 1e-12) -> HamiltonianReturn the exact Pauli decomposition as a Hamiltonian.
Uses FWHT internally. Pauli terms with |coeff| <= tol are
dropped. The all-identity component is accumulated into
:attr:qamomile.observable.Hamiltonian.constant. Qubit 0
corresponds to the least-significant bit of the matrix’s
computational-basis indices.
Parameters:
| Name | Type | Description |
|---|---|---|
tol | float | Absolute tolerance below which a Pauli coefficient is treated as zero and omitted from the result. |
Returns:
Hamiltonian — class:~qamomile.observable.Hamiltonian containing the
Hamiltonian — Pauli decomposition.
qamomile.linalg.mottonen¶
Möttönen-Vartiainen amplitude-encoding angle computation.
Pure classical preprocessing for the Möttönen amplitude-encoding
construction. Takes an amplitude vector and returns the Gray-walk
Ry (and, for complex inputs, Rz) angle vectors that the
Möttönen Gray-code emission consumes — assuming that the target
register starts in . See
:mod:qamomile.circuit.algorithm.state_preparation.mottonen_amplitude_encoding
for the gate-emission side and the all-zero-state pre-condition.
The math here is intentionally separated from gate emission so it
can also be used standalone — e.g., by hybrid-optimisation loops
that pre-compute angle vectors outside any kernel and then feed
them into amplitude_encoding_from_angles via
parameters=[...].
Reference¶
M. Möttönen, J. J. Vartiainen, V. Bergholm, M. M. Salomaa, “Transformation of quantum states using uniformly controlled rotations”, arXiv:quant-ph/0407010 (2004). Equation / section numbers cited in individual function docstrings below refer to this paper. In particular:
The Gray-code decomposition of a
k-control uniformly controlled rotation into2^ksingle-qubit rotations +2^kCNOTs is described in Section II, around Fig. 2 and the paragraph immediately after Eq. (2) (the paper does not number this as a Lemma / Theorem).The angle-basis transform
θ = M^(k) αlinking per-state rotation anglesαto elementary Gray-walk anglesθis Eq. (3) of Section II.Application to state preparation, including the iterative disentangling for general (complex) amplitudes, is in Section III, Eqs. (4)-(8).
Overview¶
| Function | Description |
|---|---|
compute_all_ry_angles_per_level | Pre-compute every level’s Ry rotation angle vector (magnitude stage). |
compute_disentangling_angles_per_level | Iteratively disentangle to obtain both Ry and Rz angles per level. |
compute_mottonen_amplitude_encoding_ry_angles | Pre-compute the flat Ry angle vector for a Möttönen encoding. |
compute_mottonen_amplitude_encoding_rz_angles | Pre-compute the flat Rz angle vector for the phase-restoration stage. |
validate_and_normalize_amplitudes | Validate an amplitude vector and return its normalised form. |
Functions¶
compute_all_ry_angles_per_level [source]¶
def compute_all_ry_angles_per_level(amplitudes: np.ndarray, num_qubits: int) -> list[np.ndarray]Pre-compute every level’s Ry rotation angle vector (magnitude stage).
Implements the recursive magnitude-stage angle formula given in
Möttönen et al., arXiv:quant-ph/0407010, Section III, Eq. (8)
(with the leaf case matching the unnumbered angle
2 \arcsin(|a_{2j}| / \sqrt{|a_{2j-1}|^2 + |a_{2j}|^2})
just before Eq. (6), equivalent to 2 atan2(|a_1|, |a_0|)
on the leaf pair). The pre-condition is the all-zero state
; the formulas below describe the
angles needed to reach a real amplitudes from there.
For each level k (0 <= k < num_qubits) the amplitude
vector is split into 2**k equal chunks. Each chunk yields one
per-control-state angle α (Eq. (8)):
Intermediate levels (
chunk_size > 2): the angle rotates the target qubit so its|1>weight matches the lower-half block norm. Usingarctan2(norm_lower, norm_upper)keeps the formula well defined when the upper half has zero norm.Leaf level (
chunk_size == 2):α = 2 * arctan2(a_1, a_0)directly (signed, so negative amplitudes are preserved without a phase stage).
For k >= 1 the per-control-state angles are then transformed
to the Gray-walk basis via :func:_to_gray_walk_basis (paper
Eq. (3)).
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | np.ndarray | Unit-norm real amplitude vector of length 2**num_qubits. |
num_qubits | int | Number of qubits in the target register. |
Returns:
list[np.ndarray] — list[np.ndarray]: A list of num_qubits arrays; the k-th
entry has length 2**k and holds the Gray-walk Ry
angles for that level.
compute_disentangling_angles_per_level [source]¶
def compute_disentangling_angles_per_level(
amplitudes: np.ndarray,
num_qubits: int,
) -> tuple[list[np.ndarray], list[np.ndarray]]Iteratively disentangle to obtain both Ry and Rz angles per level.
Implements the Möttönen disentangling sweep for general (complex)
amplitudes from Möttönen et al., arXiv:quant-ph/0407010,
Section III, Eqs. (4)-(8): the phase-equalisation rotation
Ξ_z (Eq. (4)) followed by the magnitude rotation, applied
pair-by-pair from LSB to MSB. Pre-condition is the all-zero
state ; the angles below are
those needed to reach the input amplitudes from there
(computed via the inverse sweep that disentangles the input).
At each step the amplitude vector is halved by pairing adjacent
entries; for the pair (a_0, a_1) we read off
Ry angle = 2 * arctan2(|a_1|, |a_0|)(magnitude split, paper Eq. (8) restricted to the leaf level — equivalently the unnumbered2 arcsin(...)form just before Eq. (6)),Rz angle = arg(a_1 / a_0)(phase difference, paper Eq. (5)),
and replace the pair with the single complex amplitude that
survives the implicit Rz^{-1} Ry^{-1} disentangling step
(paper Eq. (6) zeros out one of the pair entries; the
surviving amplitude is
which carries the averaged phase needed by the next outer step
of the sweep — the paper does not write β in this exact
closed form but the relation falls out of combining
Eqs. (5)-(7)). The full sweep is paper Eq. (7).
Zero-magnitude halves are handled gracefully by leaving the
corresponding angle at 0 (the underlying state has no support
there so the angle is immaterial). The returned per-level arrays
are already bit-reversed and gray-walk-transformed (paper
Eq. (3) applied per level via :func:_to_gray_walk_basis),
ready for the Möttönen Gray-walk emission.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | np.ndarray | Unit-norm complex amplitude vector of length 2**num_qubits. |
num_qubits | int | Number of qubits in the target register. |
Returns:
tuple[list[np.ndarray], list[np.ndarray]] — tuple[list[np.ndarray], list[np.ndarray]]:
(ry_angles_per_level, rz_angles_per_level) in
Gray-walk ordering. Each list has num_qubits entries;
the k-th entry holds 2**k angles for level k of
the forward emission.
compute_mottonen_amplitude_encoding_ry_angles [source]¶
def compute_mottonen_amplitude_encoding_ry_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarrayPre-compute the flat Ry angle vector for a Möttönen encoding.
Returns the Gray-walk Ry angles for the magnitude stage of the
encoding. For real inputs (or complex inputs with zero imaginary
part) this corresponds to the single-stage signed-Ry encoding.
For complex inputs with non-zero imaginary part, these are the
magnitude-stage angles only — pair with
:func:compute_mottonen_amplitude_encoding_rz_angles to obtain
the phase-stage angles needed to reproduce the full complex
state.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector of length 2**n. Real or complex; it is normalised automatically. |
Returns:
np.ndarray — np.ndarray: 1-D array of length 2**n - 1 holding the
Gray-walk Ry angles laid out level by level.
Raises:
ValueError— If the input is not a 1-D vector, the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
compute_mottonen_amplitude_encoding_rz_angles [source]¶
def compute_mottonen_amplitude_encoding_rz_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarrayPre-compute the flat Rz angle vector for the phase-restoration stage.
Returns the Gray-walk Rz angles for the phase stage of the
Möttönen encoding. For real inputs (or complex inputs with zero
imaginary part) the phase stage is unnecessary and the returned
array is all zeros. For complex inputs with non-zero imaginary
part, the returned angles together with the Ry angles from
:func:compute_mottonen_amplitude_encoding_ry_angles reproduce
the full complex state.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector of length 2**n. Real or complex; it is normalised automatically. |
Returns:
np.ndarray — np.ndarray: 1-D array of length 2**n - 1 holding the
Gray-walk Rz angles laid out level by level (all zeros for
real inputs).
Raises:
ValueError— If the input is not a 1-D vector, the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
validate_and_normalize_amplitudes [source]¶
def validate_and_normalize_amplitudes(
amplitudes: Sequence[float] | Sequence[complex] | np.ndarray,
) -> tuple[np.ndarray, int, bool]Validate an amplitude vector and return its normalised form.
The Möttönen construction (arXiv:quant-ph/0407010, Section III) works on a unit-norm amplitude vector indexed by computational basis states; this helper enforces that pre-condition so the downstream angle computation can assume a well-formed input.
Inputs may be real or complex. A complex input whose imaginary
part is identically zero (within np.allclose tolerance) is
coerced to a real array; this preserves the cheaper signed-RY fast
path for vectors that happen to arrive boxed as complex.
Parameters:
| Name | Type | Description |
|---|---|---|
amplitudes | Sequence[float] | Sequence[complex] | np.ndarray | Amplitude vector. Must be a 1-D sequence whose length is a power of two and at least 2, with at least one non-zero entry. |
Returns:
tuple[np.ndarray, int, bool] — tuple[np.ndarray, int, bool]:
(normalized, num_qubits, is_complex) where
normalized is a unit-norm np.ndarray,
num_qubits is log2(len(amplitudes)), and
is_complex is True iff the input has a non-zero
imaginary component (and therefore needs the
phase-restoration stage downstream). When is_complex
is False, normalized.dtype is float; otherwise
it is complex.
Raises:
ValueError— If the input is not a 1-D vector (e.g., a nested sequence or a 2-Dnp.ndarray), the length is not a power of two (or is less than 2, i.e., would map to a zero-qubit register), or all amplitudes are zero.
qamomile.linalg.subspace¶
Sample-based partial diagonalization in a Pauli-eigenbasis subspace.
Given a finite collection of computational- or Pauli-basis product states
{|s_i; β_i⟩} and a Hamiltonian H, build the projected Hamiltonian
and overlap matrices
H_sub[i, j] = ⟨s_i; β_i| H |s_j; β_j⟩,
S_sub[i, j] = ⟨s_i; β_i| s_j; β_j⟩,and (optionally) solve the generalized eigenvalue problem
H_sub v = λ S_sub v to recover variational eigenpairs in the
sampled subspace. When every sample is in the Z computational basis,
S_sub reduces to the identity (modulo duplicates) and the function
subspace_hamiltonian provides a vectorised XOR/parity fast path.
Conventions:
Qubit ordering: qubit
0is the least-significant bit of the computational-basis index, matching :meth:qamomile.observable.Hamiltonian.to_numpyand :class:qamomile.linalg.HermitianMatrix.Y-eigenstate phase:
|Y, 0⟩ = |+i⟩ = (|0⟩ + i|1⟩)/√2withY |+i⟩ = +|+i⟩. This is consistent with the basis-rotation pulseS† Hused to map a Y measurement onto the Z basis.Basis codes:
X = 0,Y = 1,Z = 2. Both raw integer codes andqamomile.observable.Paulienum members are accepted on input.Pauli.Iis rejected — every sample must lie in a definite X/Y/Z eigenbasis.
Overview¶
| Function | Description |
|---|---|
generalized_subspace_matrices | Build (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases. |
solve_subspace | Solve the (generalized) subspace eigenvalue problem. |
subspace_hamiltonian | Build H_sub[i, j] = ⟨s_i| H |s_j⟩ for Z-basis bitstring samples. |
Functions¶
generalized_subspace_matrices [source]¶
def generalized_subspace_matrices(
samples: Sequence[Sequence[int]] | np.ndarray,
bases: Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
) -> tuple[np.ndarray, np.ndarray]Build (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases.
Each sample |s_i; β_i⟩ = ⊗_q |β_{i,q}, s_{i,q}⟩ is a tensor
product of single-qubit Pauli eigenstates with per-qubit basis
β_{i,q} and bit s_{i,q}. The returned matrices are
H_sub[i, j] = ⟨s_i; β_i| H |s_j; β_j⟩,
S_sub[i, j] = ⟨s_i; β_i| s_j; β_j⟩,so the variational eigenpairs are recovered by solving the
generalized eigenvalue problem H_sub v = λ S_sub v (see
:func:solve_subspace).
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of bitstrings, with samples[k][q] the eigenvalue index for qubit q in basis bases[k][q] (0 for the +1 eigenstate, 1 for the -1 eigenstate). |
bases | Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | Per-qubit basis labels. Either a 1-D sequence of length num_qubits (broadcast across all samples) or a 2-D (n_samples, num_qubits) array. Each entry is either a Pauli.X / Y / Z member or the corresponding integer code 0 / 1 / 2. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian whose matrix elements are taken. |
Returns:
np.ndarray — Tuple (H_sub, S_sub) of complex ndarray s with shape
np.ndarray — (n_samples, n_samples).
Raises:
ValueError— On shape, range, or label mismatches insamplesorbases.
Example:
Mixed-basis subspace with one Z-basis sample (|0⟩) and one
X-basis sample (|+⟩) for H = Z:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import generalized_subspace_matrices
>>> H = qm_o.Z(0)
>>> samples = [(0,), (0,)] # bit=0 in each basis
>>> bases = [(qm_o.Pauli.Z,), (qm_o.Pauli.X,)] # |0⟩ and |+⟩
>>> H_sub, S_sub = generalized_subspace_matrices(samples, bases, H)
>>> S_sub # off-diagonal ⟨0|+⟩ = 1/√2, not identity
array([[1. +0.j, 0.7071+0.j],
[0.7071+0.j, 1. +0.j]])
>>> H_sub # ⟨0|Z|0⟩=1, ⟨+|Z|+⟩=0, ⟨0|Z|+⟩=1/√2
array([[1. +0.j, 0.7071+0.j],
[0.7071+0.j, 0. +0.j]])solve_subspace [source]¶
def solve_subspace(
samples: Sequence[Sequence[int]] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
*,
bases: Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | None = None,
overlap_tol: float = 1e-12,
) -> tuple[np.ndarray, np.ndarray]Solve the (generalized) subspace eigenvalue problem.
When bases is None every sample is treated as a Z-basis
bitstring, S_sub is the identity (assuming distinct samples),
and a standard Hermitian eigendecomposition of
:func:subspace_hamiltonian is returned. Otherwise the generalised
eigenvalue problem H_sub v = λ S_sub v is reduced to a standard
Hermitian one by whitening S_sub via its eigendecomposition,
using overlap_tol as the cutoff on the overlap eigenvalues. The
same projection step automatically handles a rank-deficient
S_sub (duplicate or near-duplicate samples make the overlap
singular).
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of length-num_qubits bitstrings. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian to project. |
bases | Sequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | None | Optional per-qubit or per-sample basis labels (see :func:generalized_subspace_matrices). Defaults to None, which means every qubit of every sample is in the Z basis. |
overlap_tol | float | Threshold below which an eigenvalue of S_sub is treated as zero in the rank-deficient fallback. Must be non-negative. Defaults to 1e-12. |
Returns:
np.ndarray — Tuple (eigvals, eigvecs). eigvals is a real 1-D array
np.ndarray — sorted in ascending order; eigvecs[:, k] is the variational
tuple[np.ndarray, np.ndarray] — coefficient vector of the k-th eigenstate in the basis of
tuple[np.ndarray, np.ndarray] — the supplied samples. In the rank-deficient fallback path the
tuple[np.ndarray, np.ndarray] — number of returned eigenpairs is the effective rank of
tuple[np.ndarray, np.ndarray] — S_sub.
Raises:
ValueError— Ifoverlap_tolis negative, any input fails validation in :func:subspace_hamiltonian/ :func:generalized_subspace_matrices, or every overlap eigenvalue is belowoverlap_tol(the supplied basis spans no usable directions; loweroverlap_tolor pass a better-conditioned set of samples).
Example:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import solve_subspace
>>> H = -qm_o.X(0)
>>> # Sampling the |+⟩ eigenstate of X recovers the ground state.
>>> eigvals, _ = solve_subspace(
... [(0,)], H, bases=(qm_o.Pauli.X,)
... )
>>> float(eigvals[0])
-1.0subspace_hamiltonian [source]¶
def subspace_hamiltonian(
samples: Sequence[Sequence[int]] | np.ndarray,
hamiltonian: qm_o.Hamiltonian,
) -> np.ndarrayBuild H_sub[i, j] = ⟨s_i| H |s_j⟩ for Z-basis bitstring samples.
Vectorised over all (i, j) pairs using XOR masks to detect the
X/Y support of each Pauli term and Z/Y parities for the sign and
phase. Identical bitstrings produce a degenerate row/column; dedupe
upstream if the resulting subspace must be non-degenerate.
Parameters:
| Name | Type | Description |
|---|---|---|
samples | Sequence[Sequence[int]] | np.ndarray | Iterable of length-num_qubits bitstrings, with samples[k][q] the Z-eigenvalue index (0 for |0⟩, 1 for |1⟩) of qubit q. Excess columns are truncated. |
hamiltonian | qm_o.Hamiltonian | The Hamiltonian whose matrix elements are taken. hamiltonian.constant contributes c · I on the diagonal of the projected matrix. |
Returns:
np.ndarray — Complex ndarray of shape (n_samples, n_samples) holding
np.ndarray — ⟨s_i|H|s_j⟩.
Raises:
ValueError— Ifsampleshas the wrong rank, fewer columns thanhamiltonian.num_qubits, or out-of-range entries.
Example:
>>> import qamomile.observable as qm_o
>>> from qamomile.linalg import subspace_hamiltonian
>>> H = qm_o.Z(0) + qm_o.X(1)
>>> # All four Z-basis bitstrings on 2 qubits
>>> samples = [(0, 0), (1, 0), (0, 1), (1, 1)]
>>> H_sub = subspace_hamiltonian(samples, H)
>>> H_sub.shape
(4, 4)