# SPDX-License-Identifier: MPL-2.0
from __future__ import annotations
from typing import Tuple
from VeraGridEngine.Devices.Parents.editable_device import GCProp
from VeraGridEngine.enumerations import EraSvdSolverType
from VeraGridEngine.Simulations.options_template import OptionsTemplate
[docs]
class EraMatrixPencilBand:
"""
Immutable-looking light wrapper for one analysis band.
The class exposes explicit getters and setters because the EMT modal
workflow needs predictable state inspection when debugging numerical
conditioning problems.
"""
__slots__ = (
'_low_hz',
'_high_hz',
)
def __init__(self, low_hz: float, high_hz: float) -> None:
"""
Build one frequency band definition.
:param low_hz: Lower edge in Hz.
:param high_hz: Upper edge in Hz.
:return: None.
"""
self._low_hz: float = 0.0
self._high_hz: float = 0.0
# The setters centralize all validation so every construction path
# uses the exact same band sanitization rules.
self.set_low_hz(low_hz)
self.set_high_hz(high_hz)
[docs]
def get_low_hz(self) -> float:
"""
Return the lower analysis frequency.
:return: Lower edge in Hz.
"""
return self._low_hz
[docs]
def set_low_hz(self, value: float) -> None:
"""
Set the lower analysis frequency.
:param value: Requested lower edge in Hz.
:return: None.
"""
sanitized_value: float = float(value)
# The lower edge must be non-negative because negative frequencies are
# represented by the conjugate pole pair in the real-valued signal.
if sanitized_value >= 0.0:
self._low_hz = sanitized_value
else:
self._low_hz = 0.0
[docs]
def get_high_hz(self) -> float:
"""
Return the upper analysis frequency.
:return: Upper edge in Hz.
"""
return self._high_hz
[docs]
def set_high_hz(self, value: float) -> None:
"""
Set the upper analysis frequency.
:param value: Requested upper edge in Hz.
:return: None.
"""
sanitized_value: float = float(value)
# The upper edge must stay strictly positive so the band carries a
# meaningful oscillatory window for the matrix pencil subspace.
if sanitized_value > 0.0:
self._high_hz = sanitized_value
else:
self._high_hz = 1.0
@property
def low_hz(self) -> float:
"""
Property wrapper for VeraGrid style compatibility.
:return: Lower edge in Hz.
"""
return self.get_low_hz()
@low_hz.setter
def low_hz(self, value: float) -> None:
"""
Property wrapper for VeraGrid style compatibility.
:param value: Requested lower edge in Hz.
:return: None.
"""
self.set_low_hz(value)
@property
def high_hz(self) -> float:
"""
Property wrapper for VeraGrid style compatibility.
:return: Upper edge in Hz.
"""
return self.get_high_hz()
@high_hz.setter
def high_hz(self, value: float) -> None:
"""
Property wrapper for VeraGrid style compatibility.
:param value: Requested upper edge in Hz.
:return: None.
"""
self.set_high_hz(value)
[docs]
def to_tuple(self) -> Tuple[float, float]:
"""
Export the band as a plain tuple.
:return: Two-float tuple ``(low_hz, high_hz)``.
"""
return self._low_hz, self._high_hz
[docs]
def create_default_era_analysis_bands() -> Tuple[EraMatrixPencilBand, ...]:
"""
Build the default multi-band plan for EMT modal zooming.
:return: Tuple of default analysis bands.
"""
bands_list: list[EraMatrixPencilBand] = list()
# The first band targets super-synchronous control modes after the
# fundamental notch suppresses the overwhelming 50/60 Hz component.
bands_list.append(EraMatrixPencilBand(low_hz=25.0, high_hz=250.0))
# The second band targets inverter ripple, PLL interactions and other
# higher-frequency EMT control artifacts.
bands_list.append(EraMatrixPencilBand(low_hz=250.0, high_hz=2000.0))
return tuple(bands_list)
[docs]
def build_era_analysis_bands_from_pairs(
band_limits: Tuple[Tuple[float, float], ...] | None) -> Tuple[EraMatrixPencilBand, ...]:
"""
Convert raw tuple pairs into validated analysis-band objects.
:param band_limits: Optional tuple of ``(low_hz, high_hz)`` pairs.
:return: Sanitized tuple of :class:`EraMatrixPencilBand` objects.
"""
bands_list: list[EraMatrixPencilBand] = list()
if band_limits is not None and len(band_limits) > 0:
pair_index: int = 0
n_pairs: int = len(band_limits)
# The conversion is explicit so every band is independently validated
# and invalid ranges can be ignored without corrupting the whole plan.
while pair_index < n_pairs:
current_pair: Tuple[float, float] = band_limits[pair_index]
low_hz: float = float(current_pair[0])
high_hz: float = float(current_pair[1])
if high_hz > low_hz:
bands_list.append(EraMatrixPencilBand(low_hz=low_hz, high_hz=high_hz))
else:
pass
pair_index += 1
else:
default_bands: Tuple[EraMatrixPencilBand, ...] = create_default_era_analysis_bands()
band_index: int = 0
n_default_bands: int = len(default_bands)
while band_index < n_default_bands:
default_band: EraMatrixPencilBand = default_bands[band_index]
bands_list.append(
EraMatrixPencilBand(
low_hz=default_band.get_low_hz(),
high_hz=default_band.get_high_hz(),
)
)
band_index += 1
if len(bands_list) > 0:
sanitized_bands: Tuple[EraMatrixPencilBand, ...] = tuple(bands_list)
else:
sanitized_bands = create_default_era_analysis_bands()
return sanitized_bands
[docs]
class EraMatrixPencilOptions(OptionsTemplate):
"""
Configuration object for the EMT frequency-zooming matrix pencil engine.
The class keeps the legacy public API while adding the numerical controls
required by the multi-band forward-backward TLS formulation.
"""
__slots__ = (
'_decimation_factor',
'_svd_solver',
'_tol_deflation',
'_max_modes',
'_t_ringdown',
'_verbose',
'_nominal_frequency_hz',
'_use_notch_filtering',
'_notch_quality_factor',
'_decimation_safety_factor',
'_anti_alias_filter_order',
'_use_zero_phase_filtering',
'_block_rows_ratio',
'_minimum_block_rows',
'_minimum_samples_per_band',
'_use_forward_backward',
'_use_exponential_detrending',
'_maximum_observables',
'_min_mode_energy_ratio',
'_frequency_merge_tolerance_hz',
'_real_part_merge_tolerance',
'_principal_log_tolerance_hz',
'_condition_number_limit',
'_analysis_bands',
)
LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = (
GCProp(key='decimation_factor', tpe=int),
GCProp(key='svd_solver', tpe=EraSvdSolverType),
GCProp(key='tol_deflation', tpe=float),
GCProp(key='max_modes', tpe=int),
GCProp(key='t_ringdown', tpe=float),
GCProp(key='verbose', tpe=int),
GCProp(key='nominal_frequency_hz', tpe=float),
GCProp(key='use_notch_filtering', tpe=bool),
GCProp(key='notch_quality_factor', tpe=float),
GCProp(key='decimation_safety_factor', tpe=float),
GCProp(key='anti_alias_filter_order', tpe=int),
GCProp(key='use_zero_phase_filtering', tpe=bool),
GCProp(key='block_rows_ratio', tpe=float),
GCProp(key='minimum_block_rows', tpe=int),
GCProp(key='minimum_samples_per_band', tpe=int),
GCProp(key='use_forward_backward', tpe=bool),
GCProp(key='use_exponential_detrending', tpe=bool),
GCProp(key='maximum_observables', tpe=int),
GCProp(key='min_mode_energy_ratio', tpe=float),
GCProp(key='frequency_merge_tolerance_hz', tpe=float),
GCProp(key='real_part_merge_tolerance', tpe=float),
GCProp(key='principal_log_tolerance_hz', tpe=float),
GCProp(key='condition_number_limit', tpe=float),
)
def __init__(self,
decimation_factor: int = 1,
svd_solver: EraSvdSolverType = EraSvdSolverType.FullSvd,
tol_deflation: float = 1e-8,
max_modes: int = 50,
t_ringdown: float = 0.5,
verbose: int = 0,
nominal_frequency_hz: float = 0.0,
use_notch_filtering: bool = True,
notch_quality_factor: float = 60.0,
decimation_safety_factor: float = 10.0,
anti_alias_filter_order: int = 8,
use_zero_phase_filtering: bool = True,
block_rows_ratio: float = 0.35,
minimum_block_rows: int = 8,
minimum_samples_per_band: int = 64,
use_forward_backward: bool = True,
use_exponential_detrending: bool = True,
maximum_observables: int = 0,
min_mode_energy_ratio: float = 0.01,
frequency_merge_tolerance_hz: float = 0.5,
real_part_merge_tolerance: float = 0.5,
principal_log_tolerance_hz: float = 2.0,
condition_number_limit: float = 1e12,
analysis_bands: Tuple[Tuple[float, float], ...] | None = None) -> None:
"""
Initialize the ERA/Matrix Pencil options.
:param decimation_factor: Legacy manual cap for the dynamic decimator.
:param svd_solver: SVD backend selection.
:param tol_deflation: Numerical singular-value floor.
:param max_modes: Global maximum number of modes to keep.
:param t_ringdown: EMT ringdown simulation time horizon in seconds.
:param verbose: Verbosity level.
:param nominal_frequency_hz: Explicit nominal frequency. ``0.0`` means auto.
:param use_notch_filtering: Enable the strict fundamental notch.
:param notch_quality_factor: Notch quality factor.
:param decimation_safety_factor: Oversampling safety factor after band isolation.
:param anti_alias_filter_order: Butterworth filter order used before decimation.
:param use_zero_phase_filtering: Use zero-phase filtering when feasible.
:param block_rows_ratio: Ratio used to size the block-Hankel matrix.
:param minimum_block_rows: Minimum block rows for the Hankel matrix.
:param minimum_samples_per_band: Minimum samples required to solve one band.
:param use_forward_backward: Enable forward-backward pencil symmetrization.
:param use_exponential_detrending: Enable exponential DC-offset removal.
:param maximum_observables: Optional cap on the number of channels. ``0`` means all.
:param min_mode_energy_ratio: Minimum modal-energy ratio to keep one mode.
:param frequency_merge_tolerance_hz: Frequency tolerance for modal fusion.
:param real_part_merge_tolerance: Real-part tolerance for modal fusion.
:param principal_log_tolerance_hz: Tolerance used while validating band consistency.
:param condition_number_limit: Numerical conditioning ceiling.
:param analysis_bands: Optional tuple of explicit band limits.
:return: None.
"""
OptionsTemplate.__init__(self, name='EraMatrixPencilOptions')
# Every field is initialized before validation so the setters always
# operate on already allocated state, which keeps debugging predictable.
self._decimation_factor: int = 1
self._svd_solver: EraSvdSolverType = EraSvdSolverType.FullSvd
self._tol_deflation: float = 1e-8
self._max_modes: int = 50
self._t_ringdown: float = 0.5
self._verbose: int = 0
self._nominal_frequency_hz: float = 0.0
self._use_notch_filtering: bool = True
self._notch_quality_factor: float = 60.0
self._decimation_safety_factor: float = 10.0
self._anti_alias_filter_order: int = 8
self._use_zero_phase_filtering: bool = True
self._block_rows_ratio: float = 0.35
self._minimum_block_rows: int = 8
self._minimum_samples_per_band: int = 64
self._use_forward_backward: bool = True
self._use_exponential_detrending: bool = True
self._maximum_observables: int = 0
self._min_mode_energy_ratio: float = 0.01
self._frequency_merge_tolerance_hz: float = 0.5
self._real_part_merge_tolerance: float = 0.5
self._principal_log_tolerance_hz: float = 2.0
self._condition_number_limit: float = 1e12
self._analysis_bands: Tuple[EraMatrixPencilBand, ...] = create_default_era_analysis_bands()
# The public setters keep the numeric constraints centralized.
self.set_decimation_factor(decimation_factor)
self.set_svd_solver(svd_solver)
self.set_tol_deflation(tol_deflation)
self.set_max_modes(max_modes)
self.set_t_ringdown(t_ringdown)
self.set_verbose(verbose)
self.set_nominal_frequency_hz(nominal_frequency_hz)
self.set_use_notch_filtering(use_notch_filtering)
self.set_notch_quality_factor(notch_quality_factor)
self.set_decimation_safety_factor(decimation_safety_factor)
self.set_anti_alias_filter_order(anti_alias_filter_order)
self.set_use_zero_phase_filtering(use_zero_phase_filtering)
self.set_block_rows_ratio(block_rows_ratio)
self.set_minimum_block_rows(minimum_block_rows)
self.set_minimum_samples_per_band(minimum_samples_per_band)
self.set_use_forward_backward(use_forward_backward)
self.set_use_exponential_detrending(use_exponential_detrending)
self.set_maximum_observables(maximum_observables)
self.set_min_mode_energy_ratio(min_mode_energy_ratio)
self.set_frequency_merge_tolerance_hz(frequency_merge_tolerance_hz)
self.set_real_part_merge_tolerance(real_part_merge_tolerance)
self.set_principal_log_tolerance_hz(principal_log_tolerance_hz)
self.set_condition_number_limit(condition_number_limit)
self.set_analysis_bands(analysis_bands)
[docs]
def get_decimation_factor(self) -> int:
"""
Return the legacy manual cap for dynamic decimation.
:return: Positive decimation cap.
"""
return self._decimation_factor
[docs]
def set_decimation_factor(self, value: int) -> None:
"""
Set the legacy manual cap for dynamic decimation.
:param value: Requested cap.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 1:
self._decimation_factor = sanitized_value
else:
self._decimation_factor = 1
@property
def decimation_factor(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Positive decimation cap.
"""
return self.get_decimation_factor()
@decimation_factor.setter
def decimation_factor(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested cap.
:return: None.
"""
self.set_decimation_factor(value)
[docs]
def get_svd_solver(self) -> EraSvdSolverType:
"""
Return the configured SVD backend.
:return: SVD backend enum.
"""
return self._svd_solver
[docs]
def set_svd_solver(self, value: EraSvdSolverType) -> None:
"""
Set the configured SVD backend.
:param value: Requested SVD backend.
:return: None.
"""
if isinstance(value, EraSvdSolverType):
self._svd_solver = value
else:
self._svd_solver = EraSvdSolverType.FullSvd
@property
def svd_solver(self) -> EraSvdSolverType:
"""
Property wrapper for VeraGrid schema access.
:return: SVD backend enum.
"""
return self.get_svd_solver()
@svd_solver.setter
def svd_solver(self, value: EraSvdSolverType) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested SVD backend.
:return: None.
"""
self.set_svd_solver(value)
[docs]
def get_tol_deflation(self) -> float:
"""
Return the singular-value numerical floor.
:return: Positive floor value.
"""
return self._tol_deflation
[docs]
def set_tol_deflation(self, value: float) -> None:
"""
Set the singular-value numerical floor.
:param value: Requested floor.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 0.0:
self._tol_deflation = sanitized_value
else:
self._tol_deflation = 1e-12
@property
def tol_deflation(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Positive floor value.
"""
return self.get_tol_deflation()
@tol_deflation.setter
def tol_deflation(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested floor.
:return: None.
"""
self.set_tol_deflation(value)
[docs]
def get_max_modes(self) -> int:
"""
Return the global maximum number of retained modes.
:return: Positive mode cap.
"""
return self._max_modes
[docs]
def set_max_modes(self, value: int) -> None:
"""
Set the global maximum number of retained modes.
:param value: Requested mode cap.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 1:
self._max_modes = sanitized_value
else:
self._max_modes = 1
@property
def max_modes(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Positive mode cap.
"""
return self.get_max_modes()
@max_modes.setter
def max_modes(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested mode cap.
:return: None.
"""
self.set_max_modes(value)
[docs]
def get_t_ringdown(self) -> float:
"""
Return the EMT ringdown simulation horizon.
:return: Time horizon in seconds.
"""
return self._t_ringdown
[docs]
def set_t_ringdown(self, value: float) -> None:
"""
Set the EMT ringdown simulation horizon.
:param value: Requested time horizon in seconds.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 0.0:
self._t_ringdown = sanitized_value
else:
self._t_ringdown = 0.5
@property
def t_ringdown(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Time horizon in seconds.
"""
return self.get_t_ringdown()
@t_ringdown.setter
def t_ringdown(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested time horizon in seconds.
:return: None.
"""
self.set_t_ringdown(value)
[docs]
def get_verbose(self) -> int:
"""
Return the verbosity level.
:return: Verbosity level.
"""
return self._verbose
[docs]
def set_verbose(self, value: int) -> None:
"""
Set the verbosity level.
:param value: Requested verbosity level.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 0:
self._verbose = sanitized_value
else:
self._verbose = 0
@property
def verbose(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Verbosity level.
"""
return self.get_verbose()
@verbose.setter
def verbose(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested verbosity level.
:return: None.
"""
self.set_verbose(value)
[docs]
def get_nominal_frequency_hz(self) -> float:
"""
Return the explicit nominal-frequency override.
:return: Frequency in Hz. ``0.0`` means auto-detect from the grid.
"""
return self._nominal_frequency_hz
[docs]
def set_nominal_frequency_hz(self, value: float) -> None:
"""
Set the explicit nominal-frequency override.
:param value: Frequency in Hz. ``0.0`` means auto.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value >= 0.0:
self._nominal_frequency_hz = sanitized_value
else:
self._nominal_frequency_hz = 0.0
@property
def nominal_frequency_hz(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Frequency in Hz.
"""
return self.get_nominal_frequency_hz()
@nominal_frequency_hz.setter
def nominal_frequency_hz(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested frequency in Hz.
:return: None.
"""
self.set_nominal_frequency_hz(value)
[docs]
def get_use_notch_filtering(self) -> bool:
"""
Return the notch-filter activation flag.
:return: ``True`` when the fundamental notch is enabled.
"""
return self._use_notch_filtering
[docs]
def set_use_notch_filtering(self, value: bool) -> None:
"""
Set the notch-filter activation flag.
:param value: Requested flag.
:return: None.
"""
self._use_notch_filtering = bool(value)
@property
def use_notch_filtering(self) -> bool:
"""
Property wrapper for VeraGrid schema access.
:return: ``True`` when the fundamental notch is enabled.
"""
return self.get_use_notch_filtering()
@use_notch_filtering.setter
def use_notch_filtering(self, value: bool) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested flag.
:return: None.
"""
self.set_use_notch_filtering(value)
[docs]
def get_notch_quality_factor(self) -> float:
"""
Return the notch quality factor.
:return: Positive quality factor.
"""
return self._notch_quality_factor
[docs]
def set_notch_quality_factor(self, value: float) -> None:
"""
Set the notch quality factor.
:param value: Requested quality factor.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 1.0:
self._notch_quality_factor = sanitized_value
else:
self._notch_quality_factor = 30.0
@property
def notch_quality_factor(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Positive quality factor.
"""
return self.get_notch_quality_factor()
@notch_quality_factor.setter
def notch_quality_factor(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested quality factor.
:return: None.
"""
self.set_notch_quality_factor(value)
[docs]
def get_decimation_safety_factor(self) -> float:
"""
Return the post-band oversampling safety factor.
:return: Positive oversampling factor.
"""
return self._decimation_safety_factor
[docs]
def set_decimation_safety_factor(self, value: float) -> None:
"""
Set the post-band oversampling safety factor.
:param value: Requested oversampling factor.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value >= 2.5:
self._decimation_safety_factor = sanitized_value
else:
self._decimation_safety_factor = 10.0
@property
def decimation_safety_factor(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Positive oversampling factor.
"""
return self.get_decimation_safety_factor()
@decimation_safety_factor.setter
def decimation_safety_factor(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested oversampling factor.
:return: None.
"""
self.set_decimation_safety_factor(value)
[docs]
def get_anti_alias_filter_order(self) -> int:
"""
Return the Butterworth anti-alias filter order.
:return: Positive filter order.
"""
return self._anti_alias_filter_order
[docs]
def set_anti_alias_filter_order(self, value: int) -> None:
"""
Set the Butterworth anti-alias filter order.
:param value: Requested filter order.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 2:
self._anti_alias_filter_order = sanitized_value
else:
self._anti_alias_filter_order = 8
@property
def anti_alias_filter_order(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Positive filter order.
"""
return self.get_anti_alias_filter_order()
@anti_alias_filter_order.setter
def anti_alias_filter_order(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested filter order.
:return: None.
"""
self.set_anti_alias_filter_order(value)
[docs]
def get_use_zero_phase_filtering(self) -> bool:
"""
Return the zero-phase filtering flag.
:return: ``True`` when zero-phase filtering is enabled.
"""
return self._use_zero_phase_filtering
[docs]
def set_use_zero_phase_filtering(self, value: bool) -> None:
"""
Set the zero-phase filtering flag.
:param value: Requested flag.
:return: None.
"""
self._use_zero_phase_filtering = bool(value)
@property
def use_zero_phase_filtering(self) -> bool:
"""
Property wrapper for VeraGrid schema access.
:return: ``True`` when zero-phase filtering is enabled.
"""
return self.get_use_zero_phase_filtering()
@use_zero_phase_filtering.setter
def use_zero_phase_filtering(self, value: bool) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested flag.
:return: None.
"""
self.set_use_zero_phase_filtering(value)
[docs]
def get_block_rows_ratio(self) -> float:
"""
Return the block-Hankel sizing ratio.
:return: Ratio in the interval ``(0, 1)``.
"""
return self._block_rows_ratio
[docs]
def set_block_rows_ratio(self, value: float) -> None:
"""
Set the block-Hankel sizing ratio.
:param value: Requested ratio.
:return: None.
"""
sanitized_value: float = float(value)
if 0.05 < sanitized_value < 0.75:
self._block_rows_ratio = sanitized_value
else:
self._block_rows_ratio = 0.35
@property
def block_rows_ratio(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Ratio in the interval ``(0, 1)``.
"""
return self.get_block_rows_ratio()
@block_rows_ratio.setter
def block_rows_ratio(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested ratio.
:return: None.
"""
self.set_block_rows_ratio(value)
[docs]
def get_minimum_block_rows(self) -> int:
"""
Return the minimum block-Hankel depth.
:return: Minimum block rows.
"""
return self._minimum_block_rows
[docs]
def set_minimum_block_rows(self, value: int) -> None:
"""
Set the minimum block-Hankel depth.
:param value: Requested minimum block rows.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 2:
self._minimum_block_rows = sanitized_value
else:
self._minimum_block_rows = 8
@property
def minimum_block_rows(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Minimum block rows.
"""
return self.get_minimum_block_rows()
@minimum_block_rows.setter
def minimum_block_rows(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested minimum block rows.
:return: None.
"""
self.set_minimum_block_rows(value)
[docs]
def get_minimum_samples_per_band(self) -> int:
"""
Return the minimum number of samples required for one band.
:return: Minimum number of samples.
"""
return self._minimum_samples_per_band
[docs]
def set_minimum_samples_per_band(self, value: int) -> None:
"""
Set the minimum number of samples required for one band.
:param value: Requested minimum number of samples.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 16:
self._minimum_samples_per_band = sanitized_value
else:
self._minimum_samples_per_band = 64
@property
def minimum_samples_per_band(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: Minimum number of samples.
"""
return self.get_minimum_samples_per_band()
@minimum_samples_per_band.setter
def minimum_samples_per_band(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested minimum number of samples.
:return: None.
"""
self.set_minimum_samples_per_band(value)
[docs]
def get_use_forward_backward(self) -> bool:
"""
Return the forward-backward matrix-pencil flag.
:return: ``True`` when FBMP is enabled.
"""
return self._use_forward_backward
[docs]
def set_use_forward_backward(self, value: bool) -> None:
"""
Set the forward-backward matrix-pencil flag.
:param value: Requested flag.
:return: None.
"""
self._use_forward_backward = bool(value)
@property
def use_forward_backward(self) -> bool:
"""
Property wrapper for VeraGrid schema access.
:return: ``True`` when FBMP is enabled.
"""
return self.get_use_forward_backward()
@use_forward_backward.setter
def use_forward_backward(self, value: bool) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested flag.
:return: None.
"""
self.set_use_forward_backward(value)
[docs]
def get_use_exponential_detrending(self) -> bool:
"""
Return the exponential detrending flag.
:return: ``True`` when exponential detrending is enabled.
"""
return self._use_exponential_detrending
[docs]
def set_use_exponential_detrending(self, value: bool) -> None:
"""
Set the exponential detrending flag.
:param value: Requested flag.
:return: None.
"""
self._use_exponential_detrending = bool(value)
@property
def use_exponential_detrending(self) -> bool:
"""
Property wrapper for VeraGrid schema access.
:return: ``True`` when exponential detrending is enabled.
"""
return self.get_use_exponential_detrending()
@use_exponential_detrending.setter
def use_exponential_detrending(self, value: bool) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested flag.
:return: None.
"""
self.set_use_exponential_detrending(value)
[docs]
def get_maximum_observables(self) -> int:
"""
Return the optional cap on the number of observed channels.
:return: ``0`` means all channels.
"""
return self._maximum_observables
[docs]
def set_maximum_observables(self, value: int) -> None:
"""
Set the optional cap on the number of observed channels.
:param value: Requested cap. ``0`` means all channels.
:return: None.
"""
sanitized_value: int = int(value)
if sanitized_value >= 0:
self._maximum_observables = sanitized_value
else:
self._maximum_observables = 0
@property
def maximum_observables(self) -> int:
"""
Property wrapper for VeraGrid schema access.
:return: ``0`` means all channels.
"""
return self.get_maximum_observables()
@maximum_observables.setter
def maximum_observables(self, value: int) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested cap.
:return: None.
"""
self.set_maximum_observables(value)
[docs]
def get_min_mode_energy_ratio(self) -> float:
"""
Return the minimum retained modal-energy ratio.
:return: Positive energy threshold.
"""
return self._min_mode_energy_ratio
[docs]
def set_min_mode_energy_ratio(self, value: float) -> None:
"""
Set the minimum retained modal-energy ratio.
:param value: Requested energy threshold.
:return: None.
"""
sanitized_value: float = float(value)
if 0.0 <= sanitized_value < 1.0:
self._min_mode_energy_ratio = sanitized_value
else:
self._min_mode_energy_ratio = 0.01
@property
def min_mode_energy_ratio(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Positive energy threshold.
"""
return self.get_min_mode_energy_ratio()
@min_mode_energy_ratio.setter
def min_mode_energy_ratio(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested energy threshold.
:return: None.
"""
self.set_min_mode_energy_ratio(value)
[docs]
def get_frequency_merge_tolerance_hz(self) -> float:
"""
Return the frequency tolerance used while merging poles.
:return: Frequency tolerance in Hz.
"""
return self._frequency_merge_tolerance_hz
[docs]
def set_frequency_merge_tolerance_hz(self, value: float) -> None:
"""
Set the frequency tolerance used while merging poles.
:param value: Requested tolerance in Hz.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 0.0:
self._frequency_merge_tolerance_hz = sanitized_value
else:
self._frequency_merge_tolerance_hz = 0.5
@property
def frequency_merge_tolerance_hz(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Frequency tolerance in Hz.
"""
return self.get_frequency_merge_tolerance_hz()
@frequency_merge_tolerance_hz.setter
def frequency_merge_tolerance_hz(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested tolerance in Hz.
:return: None.
"""
self.set_frequency_merge_tolerance_hz(value)
[docs]
def get_real_part_merge_tolerance(self) -> float:
"""
Return the real-part tolerance used while merging poles.
:return: Real-part tolerance in 1/s.
"""
return self._real_part_merge_tolerance
[docs]
def set_real_part_merge_tolerance(self, value: float) -> None:
"""
Set the real-part tolerance used while merging poles.
:param value: Requested tolerance in 1/s.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 0.0:
self._real_part_merge_tolerance = sanitized_value
else:
self._real_part_merge_tolerance = 0.5
@property
def real_part_merge_tolerance(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Real-part tolerance in 1/s.
"""
return self.get_real_part_merge_tolerance()
@real_part_merge_tolerance.setter
def real_part_merge_tolerance(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested tolerance in 1/s.
:return: None.
"""
self.set_real_part_merge_tolerance(value)
[docs]
def get_principal_log_tolerance_hz(self) -> float:
"""
Return the tolerance used while validating principal-log frequencies.
:return: Frequency tolerance in Hz.
"""
return self._principal_log_tolerance_hz
[docs]
def set_principal_log_tolerance_hz(self, value: float) -> None:
"""
Set the tolerance used while validating principal-log frequencies.
:param value: Requested tolerance in Hz.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value >= 0.0:
self._principal_log_tolerance_hz = sanitized_value
else:
self._principal_log_tolerance_hz = 2.0
@property
def principal_log_tolerance_hz(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Frequency tolerance in Hz.
"""
return self.get_principal_log_tolerance_hz()
@principal_log_tolerance_hz.setter
def principal_log_tolerance_hz(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested tolerance in Hz.
:return: None.
"""
self.set_principal_log_tolerance_hz(value)
[docs]
def get_condition_number_limit(self) -> float:
"""
Return the numerical conditioning ceiling.
:return: Positive condition-number limit.
"""
return self._condition_number_limit
[docs]
def set_condition_number_limit(self, value: float) -> None:
"""
Set the numerical conditioning ceiling.
:param value: Requested condition-number limit.
:return: None.
"""
sanitized_value: float = float(value)
if sanitized_value > 1.0:
self._condition_number_limit = sanitized_value
else:
self._condition_number_limit = 1e12
@property
def condition_number_limit(self) -> float:
"""
Property wrapper for VeraGrid schema access.
:return: Positive condition-number limit.
"""
return self.get_condition_number_limit()
@condition_number_limit.setter
def condition_number_limit(self, value: float) -> None:
"""
Property wrapper for VeraGrid schema access.
:param value: Requested condition-number limit.
:return: None.
"""
self.set_condition_number_limit(value)
[docs]
def set_analysis_bands(self, band_limits: Tuple[Tuple[float, float], ...] | None) -> None:
"""
Replace the complete analysis-band plan.
:param band_limits: Optional tuple of ``(low_hz, high_hz)`` pairs.
:return: None.
"""
self._analysis_bands = build_era_analysis_bands_from_pairs(band_limits)
[docs]
def get_analysis_bands(self) -> Tuple[EraMatrixPencilBand, ...]:
"""
Return the configured analysis-band plan.
:return: Tuple of :class:`EraMatrixPencilBand` objects.
"""
return self._analysis_bands
[docs]
def get_analysis_band_limits(self) -> Tuple[Tuple[float, float], ...]:
"""
Return the analysis-band plan as plain tuples.
:return: Tuple of ``(low_hz, high_hz)`` pairs.
"""
limits_list: list[Tuple[float, float]] = list()
band_index: int = 0
n_bands: int = len(self._analysis_bands)
while band_index < n_bands:
current_band: EraMatrixPencilBand = self._analysis_bands[band_index]
limits_list.append(current_band.to_tuple())
band_index += 1
return tuple(limits_list)