Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

qamomile.linalg

Matrix types upstream of :mod:qamomile.observable.

Exposes:

Overview

FunctionDescription
compute_mottonen_amplitude_encoding_ry_anglesPre-compute the flat Ry angle vector for a Möttönen encoding.
compute_mottonen_amplitude_encoding_rz_anglesPre-compute the flat Rz angle vector for the phase-restoration stage.
generalized_subspace_matricesBuild (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases.
solve_subspaceSolve the (generalized) subspace eigenvalue problem.
subspace_hamiltonianBuild H_sub[i, j] = ⟨s_i| H |s_j⟩ for Z-basis bitstring samples.
validate_and_normalize_amplitudesValidate an amplitude vector and return its normalised form.
ClassDescription
HermitianMatrixDense 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.ndarray

Pre-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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:


compute_mottonen_amplitude_encoding_rz_angles [source]

def compute_mottonen_amplitude_encoding_rz_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarray

Pre-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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:


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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable 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).
basesSequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarrayPer-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.
hamiltonianqm_o.HamiltonianThe 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:

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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable of length-num_qubits bitstrings.
hamiltonianqm_o.HamiltonianThe Hamiltonian to project.
basesSequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | NoneOptional 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_tolfloatThreshold 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:

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

subspace_hamiltonian [source]

def subspace_hamiltonian(
    samples: Sequence[Sequence[int]] | np.ndarray,
    hamiltonian: qm_o.Hamiltonian,
) -> np.ndarray

Build 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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable 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.
hamiltonianqm_o.HamiltonianThe 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:

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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:

Classes

HermitianMatrix [source]

class HermitianMatrix

Dense 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,
) -> None

Attributes

Methods

to_hamiltonian
def to_hamiltonian(self, *, tol: float = 1e-12) -> Hamiltonian

Return 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:

NameTypeDescription
tolfloatAbsolute 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

FunctionDescription
fwht_pauli_coefficientsCompute real Pauli coefficients alpha[x_mask, z_mask] via FWHT.
is_close_zeroCheck if a given floating-point value is close to zero within a small tolerance.
is_power_of_twoCheck whether n is a positive power of two.
ClassDescription
HermitianMatrixDense 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) -> Y

Qubit 0 corresponds to the least-significant bit of the matrix’s computational-basis indices.

Parameters:

NameTypeDescription
matrixnp.ndarrayDense (N, N) array with N = 2**n. Assumed Hermitian.
imag_tolfloatIf 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) -> bool

Check if a given floating-point value is close to zero within a small tolerance.

Parameters:

NameTypeDescription
valuefloatThe floating-point value to check.
abs_tolfloatAbsolute 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) -> bool

Check whether n is a positive power of two.

Parameters:

NameTypeDescription
nintInteger to test.

Returns:

boolTrue if n is a power of two, False otherwise.

Classes

HermitianMatrix [source]

class HermitianMatrix

Dense 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,
) -> None
Attributes
Methods
to_hamiltonian
def to_hamiltonian(self, *, tol: float = 1e-12) -> Hamiltonian

Return 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:

NameTypeDescription
tolfloatAbsolute 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 0n|0\rangle^{\otimes n}. 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:

Overview

FunctionDescription
compute_all_ry_angles_per_levelPre-compute every level’s Ry rotation angle vector (magnitude stage).
compute_disentangling_angles_per_levelIteratively disentangle to obtain both Ry and Rz angles per level.
compute_mottonen_amplitude_encoding_ry_anglesPre-compute the flat Ry angle vector for a Möttönen encoding.
compute_mottonen_amplitude_encoding_rz_anglesPre-compute the flat Rz angle vector for the phase-restoration stage.
validate_and_normalize_amplitudesValidate 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 0n|0\rangle^{\otimes n}; 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)):

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:

NameTypeDescription
amplitudesnp.ndarrayUnit-norm real amplitude vector of length 2**num_qubits.
num_qubitsintNumber 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 0n|0\rangle^{\otimes n}; 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

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

β=a02+a12exp ⁣(iarga0+arga12),\beta = \sqrt{|a_0|^2 + |a_1|^2} \, \exp\!\left(i\,\frac{\arg a_0 + \arg a_1}{2}\right),

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:

NameTypeDescription
amplitudesnp.ndarrayUnit-norm complex amplitude vector of length 2**num_qubits.
num_qubitsintNumber 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.ndarray

Pre-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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:


compute_mottonen_amplitude_encoding_rz_angles [source]

def compute_mottonen_amplitude_encoding_rz_angles(amplitudes: Sequence[float] | Sequence[complex] | np.ndarray) -> np.ndarray

Pre-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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:


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:

NameTypeDescription
amplitudesSequence[float] | Sequence[complex] | np.ndarrayAmplitude 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:


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:

Overview

FunctionDescription
generalized_subspace_matricesBuild (H_sub, S_sub) for samples in mixed X/Y/Z eigenbases.
solve_subspaceSolve the (generalized) subspace eigenvalue problem.
subspace_hamiltonianBuild 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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable 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).
basesSequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarrayPer-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.
hamiltonianqm_o.HamiltonianThe 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:

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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable of length-num_qubits bitstrings.
hamiltonianqm_o.HamiltonianThe Hamiltonian to project.
basesSequence[Sequence[BasisLike]] | Sequence[BasisLike] | np.ndarray | NoneOptional 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_tolfloatThreshold 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:

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

subspace_hamiltonian [source]

def subspace_hamiltonian(
    samples: Sequence[Sequence[int]] | np.ndarray,
    hamiltonian: qm_o.Hamiltonian,
) -> np.ndarray

Build 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:

NameTypeDescription
samplesSequence[Sequence[int]] | np.ndarrayIterable 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.
hamiltonianqm_o.HamiltonianThe 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:

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)