"""Support-morphology diagnostics for caged-state analysis.
These finite-size descriptors are intentionally separate from exterior-environment
reduction. They characterize where a state has support; they do not classify a
caged eigenstate or certify that an exterior environment can be removed.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal, TypeAlias
import numpy as np
import scipy.sparse as sp
from numpy.typing import NDArray
from qlinks.caging.analysis.environment import (
ReducedIZMonitorComponentGroup,
ReducedIZMonitorDecomposition,
)
FockSupportMorphologyLabel: TypeAlias = Literal[
"unknown",
"finite_size_empty",
"finite_size_singleton",
"finite_size_sector_sparse",
"finite_size_sector_dense",
"finite_size_shell_sparse",
"finite_size_shell_dense",
]
RealSpaceSupportMorphologyLabel: TypeAlias = Literal[
"unknown",
"frozen",
"partially_active",
"fully_active",
]
[docs]
@dataclass(frozen=True, slots=True)
class SupportMorphologyConfig:
"""Numerical settings for finite-size support-morphology analysis."""
amplitude_tolerance: float = 1e-10
action_tolerance: float = 1e-9
fock_dense_fraction_threshold: float = 0.5
potential_shell_tolerance: float | None = None
[docs]
@dataclass(frozen=True, slots=True)
class FockSupportMorphology:
"""Finite-size morphology diagnostics for the support in Fock space.
The ``label`` is a finite-size proxy only. Scaling labels such as
finite, polynomial, or shell-extended require comparing a family of
systems across sizes.
"""
label: FockSupportMorphologyLabel = "unknown"
support_size: int = 0
effective_support_size: float = 0.0
hilbert_size: int = 0
support_fraction: float = 0.0
effective_hilbert_fraction: float = 0.0
boundary_size: int = 0
boundary_to_support_ratio: float = 0.0
support_internal_matrix_entries: int = 0
potential_shell_value: complex | None = None
potential_shell_size: int | None = None
support_shell_fraction: float | None = None
effective_shell_fraction: float | None = None
potential_shell_residual: float | None = None
@property
def has_potential_shell(self) -> bool:
return self.potential_shell_size is not None
[docs]
@dataclass(frozen=True, slots=True)
class RealSpaceSupportMorphology:
"""Finite-size morphology diagnostics in the microscopic variable space.
The variable indices are model-layout indices. Connectivity, diameter,
and winding/wrapping require lattice adjacency metadata and are therefore
left to higher-level lattice-aware helpers.
"""
label: RealSpaceSupportMorphologyLabel = "unknown"
n_variables: int = 0
active_variable_indices: tuple[int, ...] = ()
active_variable_count: int = 0
active_variable_fraction: float = 0.0
frozen_variable_count: int = 0
reduced_iz_region_variable_indices: tuple[int, ...] = ()
reduced_iz_region_variable_count: int = 0
reduced_iz_region_variable_fraction: float = 0.0
exact_support_component_count: int = 0
exact_support_component_sizes: tuple[int, ...] = ()
connected_support_component_count: int = 0
connected_support_component_sizes: tuple[int, ...] = ()
[docs]
def analyze_fock_support_morphology(
*,
full_state: NDArray[np.complex128],
kinetic_csr: sp.csr_array,
support_mask: NDArray[np.bool_],
active_frontier_zero_indices: NDArray[np.int64],
potential_diagonal: NDArray | None,
config: SupportMorphologyConfig,
) -> FockSupportMorphology:
"""Return finite-size Fock-space support morphology diagnostics."""
support_indices = np.flatnonzero(support_mask)
support_size = int(support_indices.size)
hilbert_size = int(full_state.size)
support_fraction = support_size / float(hilbert_size) if hilbert_size else 0.0
weights = np.abs(full_state) ** 2
state_norm_sq = float(np.sum(weights))
weights_fourth_sum = float(np.sum(weights**2))
if state_norm_sq > 0.0 and weights_fourth_sum > 0.0:
effective_support_size = (state_norm_sq * state_norm_sq) / weights_fourth_sum
else:
effective_support_size = 0.0
effective_hilbert_fraction = (
effective_support_size / float(hilbert_size) if hilbert_size else 0.0
)
boundary_size = int(active_frontier_zero_indices.size)
boundary_to_support_ratio = boundary_size / float(support_size) if support_size else 0.0
if support_size:
support_internal_matrix_entries = int(
kinetic_csr[support_indices, :][:, support_indices].nnz
)
else:
support_internal_matrix_entries = 0
potential_shell_value: complex | None = None
potential_shell_size: int | None = None
support_shell_fraction: float | None = None
effective_shell_fraction: float | None = None
potential_shell_residual: float | None = None
if potential_diagonal is not None:
diagonal = np.asarray(potential_diagonal, dtype=np.complex128).reshape(-1)
if diagonal.size != hilbert_size:
raise ValueError("potential_diagonal must have length full_state.size.")
if state_norm_sq > 0.0:
potential_shell_value = complex(
np.vdot(full_state, diagonal * full_state) / state_norm_sq
)
residual_vector = (diagonal - potential_shell_value) * full_state
potential_shell_residual = float(np.linalg.norm(residual_vector))
shell_tolerance = (
float(config.potential_shell_tolerance)
if config.potential_shell_tolerance is not None
else max(float(config.action_tolerance), float(config.amplitude_tolerance))
)
if potential_shell_residual <= shell_tolerance:
shell_mask = np.abs(diagonal - potential_shell_value) <= shell_tolerance
potential_shell_size = int(np.count_nonzero(shell_mask))
if potential_shell_size > 0:
support_shell_fraction = support_size / float(potential_shell_size)
effective_shell_fraction = effective_support_size / float(potential_shell_size)
if support_size == 0:
label: FockSupportMorphologyLabel = "finite_size_empty"
elif support_size == 1:
label = "finite_size_singleton"
elif effective_shell_fraction is not None:
if effective_shell_fraction >= config.fock_dense_fraction_threshold:
label = "finite_size_shell_dense"
else:
label = "finite_size_shell_sparse"
elif effective_hilbert_fraction >= config.fock_dense_fraction_threshold:
label = "finite_size_sector_dense"
else:
label = "finite_size_sector_sparse"
return FockSupportMorphology(
label=label,
support_size=support_size,
effective_support_size=float(effective_support_size),
hilbert_size=hilbert_size,
support_fraction=float(support_fraction),
effective_hilbert_fraction=float(effective_hilbert_fraction),
boundary_size=boundary_size,
boundary_to_support_ratio=float(boundary_to_support_ratio),
support_internal_matrix_entries=support_internal_matrix_entries,
potential_shell_value=potential_shell_value,
potential_shell_size=potential_shell_size,
support_shell_fraction=support_shell_fraction,
effective_shell_fraction=effective_shell_fraction,
potential_shell_residual=potential_shell_residual,
)
[docs]
def analyze_real_space_support_morphology(
*,
basis_configs: NDArray[np.integer],
support_mask: NDArray[np.bool_],
reduced_iz_region_variable_indices: tuple[int, ...],
reduced_iz_monitor_component_groups: dict[
ReducedIZMonitorDecomposition,
tuple[ReducedIZMonitorComponentGroup, ...],
],
) -> RealSpaceSupportMorphology:
"""Return finite-size variable-space support morphology diagnostics."""
n_variables = int(basis_configs.shape[1])
support_configs = basis_configs[support_mask]
if support_configs.shape[0] == 0 or n_variables == 0:
active_variable_indices: tuple[int, ...] = ()
else:
reference = support_configs[0]
active_mask = np.any(support_configs != reference, axis=0)
active_variable_indices = tuple(int(index) for index in np.flatnonzero(active_mask))
active_variable_count = len(active_variable_indices)
active_variable_fraction = active_variable_count / float(n_variables) if n_variables else 0.0
frozen_variable_count = n_variables - active_variable_count
if n_variables == 0:
label: RealSpaceSupportMorphologyLabel = "unknown"
elif active_variable_count == 0:
label = "frozen"
elif active_variable_count == n_variables:
label = "fully_active"
else:
label = "partially_active"
exact_groups = reduced_iz_monitor_component_groups.get("exact_support", ())
connected_groups = reduced_iz_monitor_component_groups.get("connected_support", ())
exact_sizes = tuple(int(group.support_size) for group in exact_groups)
connected_sizes = tuple(int(group.support_size) for group in connected_groups)
reduced_count = len(reduced_iz_region_variable_indices)
return RealSpaceSupportMorphology(
label=label,
n_variables=n_variables,
active_variable_indices=active_variable_indices,
active_variable_count=active_variable_count,
active_variable_fraction=float(active_variable_fraction),
frozen_variable_count=frozen_variable_count,
reduced_iz_region_variable_indices=reduced_iz_region_variable_indices,
reduced_iz_region_variable_count=reduced_count,
reduced_iz_region_variable_fraction=(
reduced_count / float(n_variables) if n_variables else 0.0
),
exact_support_component_count=len(exact_groups),
exact_support_component_sizes=exact_sizes,
connected_support_component_count=len(connected_groups),
connected_support_component_sizes=connected_sizes,
)
[docs]
@dataclass(frozen=True, slots=True)
class SupportMorphologyReport:
"""Finite-size Fock- and real-space support descriptors."""
fock: FockSupportMorphology
real_space: RealSpaceSupportMorphology
[docs]
def analyze_support_morphology(
*,
full_state: NDArray[np.complex128],
kinetic_matrix: sp.spmatrix | sp.sparray | NDArray,
basis_configs: NDArray[np.integer],
environment_report: object | None = None,
potential_diagonal: NDArray | None = None,
config: SupportMorphologyConfig | None = None,
) -> SupportMorphologyReport:
"""Analyze state-support morphology independently of environment reduction.
``environment_report`` is optional. When provided, its reduced-IZ region and
component decomposition are used only to annotate real-space support; they
do not affect the Fock-space morphology or any environment-removal claim.
"""
if config is None:
config = SupportMorphologyConfig()
state = np.asarray(full_state, dtype=np.complex128).reshape(-1)
configs = np.asarray(basis_configs)
if configs.ndim != 2 or configs.shape[0] != state.size:
raise ValueError("basis_configs must have shape (full_state.size, n_variables).")
kinetic_csr = sp.csr_array(kinetic_matrix)
support_mask = np.abs(state) > config.amplitude_tolerance
support_indices = np.flatnonzero(support_mask)
if support_indices.size:
incoming = np.asarray(kinetic_csr[:, support_indices].count_nonzero(axis=1)).reshape(-1) > 0
frontier = np.flatnonzero(incoming & ~support_mask).astype(np.int64, copy=False)
else:
frontier = np.array([], dtype=np.int64)
if environment_report is None:
region_variables: tuple[int, ...] = ()
component_groups: dict[
ReducedIZMonitorDecomposition,
tuple[ReducedIZMonitorComponentGroup, ...],
] = {}
else:
region_variables = tuple(
int(index)
for index in getattr(environment_report, "reduced_iz_region_variable_indices", ())
)
component_groups = dict(
getattr(environment_report, "reduced_iz_monitor_component_groups", {})
)
return SupportMorphologyReport(
fock=analyze_fock_support_morphology(
full_state=state,
kinetic_csr=kinetic_csr,
support_mask=support_mask,
active_frontier_zero_indices=frontier,
potential_diagonal=potential_diagonal,
config=config,
),
real_space=analyze_real_space_support_morphology(
basis_configs=configs,
support_mask=support_mask,
reduced_iz_region_variable_indices=region_variables,
reduced_iz_monitor_component_groups=component_groups,
),
)