Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,10 @@ class PauliEvolutionAnsatz:
def __init__(self, hamiltonian: Hamiltonian) -> None:
"""Creates a new ``PauliEvolutionAnsatz``.

The Hamiltonian is automatically **simplified** before processing:
Pauli phases are absorbed into coefficients, duplicate terms are merged,
and near-zero terms are removed. The default strategy is
The Hamiltonian is normalized before processing: Pauli phases are absorbed,
duplicate terms are merged, and imaginary residuals of magnitude <= 1e-10
are projected to zero. Larger imaginary parts are rejected; every nonzero
real coefficient is retained. The default strategy is
:meth:`EvolutionStrategy.auto` with ``steps=1``.

Args:
Expand Down
15 changes: 15 additions & 0 deletions crates/binding-python/cqlib/qis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@
# Hamiltonian module
from .hamiltonian import Hamiltonian as Hamiltonian

from . import fermion as fermion
from .fermion import (
FermionicLadder as FermionicLadder,
FermionicOp as FermionicOp,
FermionToQubitMapper as FermionToQubitMapper,
OccupationStateMapper as OccupationStateMapper,
JordanWignerMapper as JordanWignerMapper,
)

# Evolution module
from .evolution import TrotterMode as TrotterMode

Expand Down Expand Up @@ -103,6 +112,12 @@ def num_qubits(self) -> int: ...
"Pauli",
"PauliString",
"Hamiltonian",
"fermion",
"FermionicLadder",
"FermionicOp",
"FermionToQubitMapper",
"OccupationStateMapper",
"JordanWignerMapper",
"TrotterMode",
"state",
"Statevector",
Expand Down
15 changes: 15 additions & 0 deletions crates/binding-python/cqlib/qis/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ from .pauli import PauliString as PauliString
# Hamiltonian module
from .hamiltonian import Hamiltonian as Hamiltonian

from . import fermion as fermion
from .fermion import (
FermionicLadder as FermionicLadder,
FermionicOp as FermionicOp,
FermionToQubitMapper as FermionToQubitMapper,
OccupationStateMapper as OccupationStateMapper,
JordanWignerMapper as JordanWignerMapper,
)

# Evolution module
from .evolution import TrotterMode as TrotterMode

Expand Down Expand Up @@ -83,6 +92,12 @@ __all__ = [
"Pauli",
"PauliString",
"Hamiltonian",
"fermion",
"FermionicLadder",
"FermionicOp",
"FermionToQubitMapper",
"OccupationStateMapper",
"JordanWignerMapper",
"TrotterMode",
"state",
"Statevector",
Expand Down
65 changes: 65 additions & 0 deletions crates/binding-python/cqlib/qis/fermion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This code is part of Cqlib.
#
# (C) Copyright China Telecom Quantum Group 2026
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Structured fermionic algebra and fermion-to-qubit mappings.

Modes are numbered from zero. A product A * B applies B first. Jordan-Wigner
maps mode p to qubit p, with a Z chain on modes below p; qubit 0 is the least
significant bit. Core imposes no chemical or spin-orbital ordering.

The excitation generator is G = i(T - T†), where targets supply creators in
input order and sources supply annihilators in reverse input order. Evolving
the mapped generator implements exp(-i theta G) = exp(theta (T - T†)).
"""

from __future__ import annotations

from typing import Protocol, Sequence, runtime_checkable

from .._native import qis as _qis_module
from .hamiltonian import Hamiltonian

FermionicLadder = _qis_module.fermion.FermionicLadder
FermionicOp = _qis_module.fermion.FermionicOp
JordanWignerMapper = _qis_module.fermion.JordanWignerMapper


@runtime_checkable
class FermionToQubitMapper(Protocol):
"""Structural contract for operator mappings, including non-Hermitian inputs.

Implementations need not inherit this protocol. Runtime checks establish
member presence only; callers must still honor signatures and semantics.
"""

def map_operator(self, op: FermionicOp) -> Hamiltonian: ...
def num_qubits(self, num_modes: int) -> int: ...


@runtime_checkable
class OccupationStateMapper(Protocol):
"""Separate contract for computational-basis occupation encodings.

occupation[p] denotes mode p and returned bits[p] addresses qubit p.
Not every operator mapping has a computational-basis occupation encoding.
"""

def map_occupation(self, occupation: Sequence[int]) -> list[bool]: ...


__all__ = [
"FermionicLadder",
"FermionicOp",
"FermionToQubitMapper",
"OccupationStateMapper",
"JordanWignerMapper",
]
149 changes: 149 additions & 0 deletions crates/binding-python/cqlib/qis/fermion.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# This code is part of Cqlib.
#
# (C) Copyright China Telecom Quantum Group 2026
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Typed interfaces for structured fermionic algebra and sparse mappings."""

from typing import (
Iterable,
Literal,
Protocol,
Sequence,
TypeAlias,
final,
runtime_checkable,
)
from .hamiltonian import Hamiltonian

_Coefficient: TypeAlias = complex | float | int

@final
class FermionicLadder:
"""Immutable ladder, constructed with create or annihilate, not directly."""
@staticmethod
def create(mode: int) -> FermionicLadder: ...
@staticmethod
def annihilate(mode: int) -> FermionicLadder: ...
@property
def mode(self) -> int: ...
@property
def kind(self) -> Literal["create", "annihilate"]: ...
def adjoint(self) -> FermionicLadder: ...
def __eq__(self, other: object, /) -> bool: ...
def __hash__(self) -> int: ...
def __repr__(self) -> str: ...

@final
class FermionicOp:
"""Mutable complex sum of ordered products. B acts first in A * B.

Identical ordered products merge exactly; no implicit normal ordering or
numerical truncation occurs. Mode counts are retained for zero and identity.
"""

__hash__: None # type: ignore[assignment]
def __new__(cls, num_modes: int) -> FermionicOp: ...
@staticmethod
def identity(num_modes: int) -> FermionicOp: ...
@staticmethod
def from_term(
num_modes: int,
term: Iterable[FermionicLadder],
coefficient: _Coefficient = 1.0,
) -> FermionicOp: ...
@staticmethod
def from_terms(
num_modes: int,
terms: Iterable[tuple[Iterable[FermionicLadder], _Coefficient]],
) -> FermionicOp: ...
@staticmethod
def creation(num_modes: int, mode: int) -> FermionicOp: ...
@staticmethod
def annihilation(num_modes: int, mode: int) -> FermionicOp: ...
@staticmethod
def number(num_modes: int, mode: int) -> FermionicOp: ...
@staticmethod
def total_number(num_modes: int) -> FermionicOp: ...
@staticmethod
def excitation_generator(
num_modes: int, sources: Sequence[int], targets: Sequence[int]
) -> FermionicOp:
"""G = i(T - T†); targets in order, source annihilators in reverse order.

Lists must be nonempty, equally sized, unique and disjoint. Input order
is preserved, with no implicit sorting or factorial normalization.
"""
...
@property
def num_modes(self) -> int: ...
@property
def num_terms(self) -> int: ...
@property
def terms(self) -> list[tuple[tuple[FermionicLadder, ...], complex]]:
"""Independent snapshot in deterministic structural order."""
...
def is_zero(self) -> bool:
"""Whether the stored sum is empty; use equiv to test algebraic zero."""
...
def add_term(
self, term: Iterable[FermionicLadder], coefficient: _Coefficient = 1.0
) -> None:
"""Modify in place; errors leave this object unchanged."""
...
def adjoint(self) -> FermionicOp: ...
def normal_order(self) -> FermionicOp:
"""Return a new expansion: creators first, ascending modes in each group."""
...
def scaled(self, factor: _Coefficient) -> FermionicOp: ...
def chop(self, *, atol: float = 0.0) -> None:
"""Remove terms with complex magnitude <= atol, in place."""
...
def equiv(self, other: FermionicOp, *, atol: float = 0.0) -> bool: ...
def is_hermitian(self, *, atol: float = 0.0) -> bool: ...
def __add__(self, other: FermionicOp, /) -> FermionicOp: ...
def __sub__(self, other: FermionicOp, /) -> FermionicOp: ...
def __mul__(self, other: FermionicOp | _Coefficient, /) -> FermionicOp: ...
def __rmul__(self, other: _Coefficient, /) -> FermionicOp: ...
def __neg__(self) -> FermionicOp: ...
def __eq__(self, other: object, /) -> bool:
"""Structural equality; use equiv for algebraic equivalence."""
...
def copy(self) -> FermionicOp: ...
def __copy__(self) -> FermionicOp: ...
def __deepcopy__(self, memo: dict[int, object]) -> FermionicOp: ...
def __repr__(self) -> str: ...
def __str__(self) -> str: ...

@runtime_checkable
class FermionToQubitMapper(Protocol):
def map_operator(self, op: FermionicOp) -> Hamiltonian: ...
def num_qubits(self, num_modes: int) -> int: ...

@runtime_checkable
class OccupationStateMapper(Protocol):
def map_occupation(self, occupation: Sequence[int]) -> list[bool]: ...

@final
class JordanWignerMapper:
"""Mode p maps to qubit p; Z chain uses modes below p. Qubit 0 is LSB."""
def __init__(self) -> None: ...
def map_operator(self, op: FermionicOp) -> Hamiltonian: ...
def num_qubits(self, num_modes: int) -> int: ...
def map_occupation(self, occupation: Sequence[int]) -> list[bool]: ...
def __repr__(self) -> str: ...

__all__ = [
"FermionicLadder",
"FermionicOp",
"FermionToQubitMapper",
"OccupationStateMapper",
"JordanWignerMapper",
]
4 changes: 4 additions & 0 deletions crates/binding-python/cqlib/qis/hamiltonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@

"""Hamiltonian observable wrapper."""

from typing import TypeAlias

from .._native import qis as _qis_module

Coefficient: TypeAlias = float | int | complex | tuple[float, float]

Hamiltonian = _qis_module.Hamiltonian

__all__ = ["Hamiltonian"]
56 changes: 52 additions & 4 deletions crates/binding-python/cqlib/qis/hamiltonian.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

from typing import List, Tuple, Union, final, Sequence, Dict
from typing import List, Tuple, Union, final, Sequence, Dict, Iterable

import numpy as np

Expand Down Expand Up @@ -180,6 +180,52 @@ class Hamiltonian:
"""Returns ``True`` if all Pauli terms mutually commute."""
...

@staticmethod
def from_terms(
num_qubits: int,
terms: Iterable[tuple[PauliString, complex | float | int]],
) -> Hamiltonian:
"""Validate, absorb phases and merge duplicates, removing exact zeros only."""
...

def normalized(self) -> Hamiltonian:
"""Return a validated, merged copy without numerical truncation."""
...

def normalized_hermitian(self) -> Hamiltonian:
"""Normalize and project imaginary residuals <= 1e-10; reject larger ones."""
...

def add(self, other: Hamiltonian) -> Hamiltonian:
"""Return a validated, merged sum; legacy + and += retain concatenation."""
...

def scaled(self, factor: complex | float | int) -> Hamiltonian: ...
def adjoint(self) -> Hamiltonian: ...
def chop(self, *, atol: float = 0.0) -> None:
"""Normalize and truncate by complex magnitude, in place and atomically."""
...

def equiv(self, other: Hamiltonian, *, atol: float = 0.0) -> bool: ...
def is_hermitian(self, *, atol: float = 0.0) -> bool:
"""Compare with adjoint; coefficient criterion is 2*abs(imag) <= atol."""
...

def tensor(self, other: Hamiltonian) -> Hamiltonian:
"""Return self tensor other, placing other on low qubits."""
...

def remap_qubits(self, mapping: Sequence[int], target_width: int) -> Hamiltonian:
"""Embed mapping[source] = destination, with identity on unused targets."""
...

def __sub__(self, other: Hamiltonian, /) -> Hamiltonian: ...
def __mul__(self, other: Hamiltonian | complex | float | int, /) -> Hamiltonian:
"""Operator product AB (B acts first), or scalar multiplication."""
...

def __rmul__(self, other: complex | float | int, /) -> Hamiltonian: ...
def __neg__(self) -> Hamiltonian: ...
def to_matrix(self) -> np.ndarray:
"""Computes the dense matrix representation as a NumPy array.

Expand Down Expand Up @@ -247,7 +293,7 @@ class Hamiltonian:
"""
...

def __add__(self, other: "Hamiltonian") -> "Hamiltonian":
def __add__(self, other: "Hamiltonian", /) -> "Hamiltonian":
"""Adds two Hamiltonians together.

Note: This performs a simple lazy concatenation of the term lists.
Expand All @@ -273,7 +319,7 @@ class Hamiltonian:
"""
...

def __iadd__(self, other: "Hamiltonian") -> "Hamiltonian":
def __iadd__(self, other: "Hamiltonian", /) -> "Hamiltonian":
"""In-place addition of another Hamiltonian.

Args:
Expand All @@ -287,7 +333,7 @@ class Hamiltonian:
"""
...

def __eq__(self, other: object) -> bool:
def __eq__(self, other: object, /) -> bool:
"""Checks if two Hamiltonians are equal."""
...

Expand Down Expand Up @@ -365,3 +411,5 @@ class Hamiltonian:
def __deepcopy__(self, memo: dict) -> "Hamiltonian":
"""Return an independent copy via copy.deepcopy()."""
...

__all__ = ["Hamiltonian"]
Loading
Loading