nirs4all.synthesis.wavenumber module
Wavenumber conversion utilities for NIR spectroscopy.
This module provides utilities for converting between wavenumber (cm⁻¹) and wavelength (nm) units, which is essential for physically-correct band placement.
Harmonic relationships (overtones, combinations) are LINEAR in wavenumber space, not in wavelength space. This is a critical distinction for generating realistic synthetic spectra with proper overtone and combination band placement.
- Mathematical relationships:
λ (nm) = 10^7 / ν̃ (cm⁻¹)
ν̃ (cm⁻¹) = 10^7 / λ (nm)
Δλ ≈ Δν̃ × λ² / 10^7 (bandwidth conversion)
References
Siesler, H. W., Ozaki, Y., Kawata, S., & Heise, H. M. (2002). Near-Infrared Spectroscopy: Principles, Instruments, Applications. Wiley-VCH.
Workman Jr, J., & Weyer, L. (2012). Practical Guide and Spectral Atlas for Interpretive Near-Infrared Spectroscopy. CRC Press.
- class nirs4all.synthesis.wavenumber.CombinationBandResult(mode1_cm: float, mode2_cm: float, wavenumber_cm: float, wavelength_nm: float, amplitude_factor: float, band_type: str)[source]
Bases:
objectResult of combination band calculation.
- class nirs4all.synthesis.wavenumber.OvertoneResult(order: int, wavenumber_cm: float, wavelength_nm: float, amplitude_factor: float, bandwidth_factor: float)[source]
Bases:
objectResult of overtone calculation.
- nirs4all.synthesis.wavenumber.apply_hydrogen_bonding_shift(wavenumber_cm: float, h_bond_strength: float = 0.5, is_donor: bool = True) float[source]
Apply hydrogen bonding shift to a wavenumber.
Hydrogen bonding weakens X-H bonds, shifting stretching frequencies to lower wavenumbers (red shift). The shift magnitude depends on the hydrogen bond strength.
Typical shifts for O-H: - Free O-H: ~3650 cm⁻¹ - Weak H-bond: ~3500 cm⁻¹ - Strong H-bond: ~3200 cm⁻¹
- Parameters:
wavenumber_cm – Original wavenumber in cm⁻¹.
h_bond_strength – Hydrogen bond strength (0 = none, 1 = very strong).
is_donor – Whether the group is a hydrogen bond donor.
- Returns:
Shifted wavenumber in cm⁻¹.
Example
>>> apply_hydrogen_bonding_shift(3650, h_bond_strength=0.5) 3467.5 # Red-shifted by hydrogen bonding
- nirs4all.synthesis.wavenumber.calculate_combination_band(mode1: str | float | List[str | float], mode2: str | float | None = None, band_type: str = 'sum', coupling_factor: float = 1.0) CombinationBandResult[source]
Calculate combination band position.
Combination bands arise from simultaneous excitation of two vibrational modes. - Sum bands: ν̃_comb = ν̃₁ + ν̃₂ (most common in NIR) - Difference bands: ν̃_comb = |ν̃₁ - ν̃₂| (less common)
- Parameters:
mode1 – First vibration - either a vibration type string (e.g., ‘O-H_stretch’), a numeric wavenumber in cm⁻¹, or a list of two modes.
mode2 – Second vibration (same format as mode1). If mode1 is a list, this should be None.
band_type – ‘sum’ or ‘difference’.
coupling_factor – Mechanical coupling between modes (0-1, affects amplitude).
- Returns:
CombinationBandResult with position and intensity information.
Example
>>> # O-H stretch + O-H bend combination (water) using strings >>> result = calculate_combination_band("O-H_stretch", "O-H_bend") >>> print(f"{result.wavelength_nm:.0f} nm") 1984 nm
>>> # Using a list of modes >>> result = calculate_combination_band(["O-H_stretch", "O-H_bend"])
>>> # Using numeric values >>> result = calculate_combination_band(3400, 1640)
- nirs4all.synthesis.wavenumber.calculate_overtone_position(vibration_type_or_frequency: str | float, overtone_order: int, anharmonicity: float | None = None) OvertoneResult[source]
Calculate overtone band position with anharmonicity correction.
For a harmonic oscillator, overtones would be exactly at n × ν̃₀. However, real molecular vibrations are anharmonic, causing overtones to appear at slightly lower wavenumbers than the harmonic prediction.
The anharmonic wavenumber is: ν̃ₙ = n × ν̃₀ × (1 - n × χ) where χ is the anharmonicity constant (typically 0.01-0.03).
- Parameters:
vibration_type_or_frequency – Either a vibration type string (e.g., ‘O-H_stretch’) from FUNDAMENTAL_VIBRATIONS, or a numeric fundamental frequency in cm⁻¹.
overtone_order – Order (1 = fundamental, 2 = 1st overtone, 3 = 2nd overtone).
anharmonicity – Anharmonicity constant χ. If None and vibration_type is a string, uses the default for that vibration type. Otherwise defaults to 0.02.
- Returns:
OvertoneResult with position and intensity information.
Example
>>> result = calculate_overtone_position("O-H_stretch", 2) # O-H 1st overtone >>> print(f"{result.wavelength_nm:.0f} nm") 1442 nm # (with anharmonicity)
>>> result = calculate_overtone_position(3400, 2) # Numeric frequency >>> print(f"{result.wavelength_nm:.0f} nm") 1503 nm
- nirs4all.synthesis.wavenumber.classify_wavelength_extended(wavelength_nm: float) Tuple[str, str] | None[source]
Classify a wavelength into extended spectral zones (Vis-NIR: 350-2500 nm).
This function covers both visible (electronic transitions) and NIR (vibrational overtones/combinations) regions.
- Parameters:
wavelength_nm – Wavelength in nm.
- Returns:
Tuple of (zone_name, description), or None if outside defined zones.
Example
>>> classify_wavelength_extended(450) ('blue_absorption', 'Soret bands, carotenoid peak absorptions') >>> classify_wavelength_extended(660) ('red_absorption', 'Chlorophyll Q bands, hemoglobin bands') >>> classify_wavelength_extended(1450) ('1st_overtones_OH_NH', '1st overtones O-H, N-H')
- nirs4all.synthesis.wavenumber.classify_wavelength_zone(wavelength_nm: float) str | None[source]
Classify a wavelength into its corresponding NIR zone.
- Parameters:
wavelength_nm – Wavelength in nm.
- Returns:
Zone name string, or None if outside defined zones.
Example
>>> classify_wavelength_zone(1450) '1st_overtones_OH_NH' >>> classify_wavelength_zone(2300) 'combination_CH'
- nirs4all.synthesis.wavenumber.convert_bandwidth_to_wavelength(bandwidth_cm: float, center_nm: float) float[source]
Convert bandwidth from wavenumber to wavelength units.
Since the relationship between wavenumber and wavelength is non-linear, the bandwidth conversion depends on the center wavelength/wavenumber.
The approximation is: Δλ ≈ Δν̃ × λ² / 10^7
This is derived from the differential: dλ = -dν̃ × (10^7 / ν̃²) = -dν̃ × λ² / 10^7 (taking absolute value for bandwidth)
- Parameters:
bandwidth_cm – Bandwidth in cm⁻¹ (e.g., FWHM).
center_nm – Center wavelength in nm.
- Returns:
Bandwidth in nm.
Example
>>> convert_bandwidth_to_wavelength(100, 1450) # 100 cm⁻¹ at 1450 nm 21.025 # approximately >>> convert_bandwidth_to_wavelength(100, 2200) # Same bandwidth at 2200 nm 48.4 # Broader in nm due to non-linear relationship
- nirs4all.synthesis.wavenumber.convert_bandwidth_to_wavenumber(bandwidth_nm: float, center_nm: float) float[source]
Convert bandwidth from wavelength to wavenumber units.
The inverse of convert_bandwidth_to_wavelength: Δν̃ ≈ Δλ × 10^7 / λ²
- Parameters:
bandwidth_nm – Bandwidth in nm (e.g., FWHM).
center_nm – Center wavelength in nm.
- Returns:
Bandwidth in cm⁻¹.
Example
>>> convert_bandwidth_to_wavenumber(25, 1450) # 25 nm at 1450 nm 118.9 # approximately
- nirs4all.synthesis.wavenumber.estimate_bandwidth_broadening(baseline_bandwidth_cm: float, h_bond_strength: float = 0.0, temperature_k: float = 298.0) float[source]
Estimate bandwidth broadening due to environmental effects.
Hydrogen bonding and temperature increase band widths through: - Distribution of H-bond geometries (inhomogeneous broadening) - Thermal population of vibrational levels
- Parameters:
baseline_bandwidth_cm – Intrinsic bandwidth in cm⁻¹.
h_bond_strength – Hydrogen bond strength (0-1).
temperature_k – Temperature in Kelvin.
- Returns:
Broadened bandwidth in cm⁻¹.
Example
>>> estimate_bandwidth_broadening(50, h_bond_strength=0.7) 85.0 # Broadened by hydrogen bonding
- nirs4all.synthesis.wavenumber.get_all_zones_extended() List[Tuple[float, float, str, str]][source]
Get all extended spectral zones (Vis-NIR) converted to wavelength space.
- Returns:
List of (min_wavelength, max_wavelength, zone_name, description) tuples in nm.
Example
>>> zones = get_all_zones_extended() >>> for min_wl, max_wl, name, desc in zones: ... print(f"{name}: {min_wl:.0f}-{max_wl:.0f} nm - {desc}")
- nirs4all.synthesis.wavenumber.get_all_zones_wavelength() List[Tuple[float, float, str]][source]
Get all NIR zones converted to wavelength space.
- Returns:
List of (min_wavelength, max_wavelength, zone_name) tuples in nm.
Example
>>> zones = get_all_zones_wavelength() >>> for min_wl, max_wl, name in zones: ... print(f"{name}: {min_wl:.0f}-{max_wl:.0f} nm")
- nirs4all.synthesis.wavenumber.get_nir_overtones_for_fundamental(fundamental_cm: float, max_order: int = 4, wavelength_range: Tuple[float, float] = (800, 2500), anharmonicity: float = 0.02) List[OvertoneResult][source]
Get all overtones of a fundamental that fall within the NIR range.
- Parameters:
fundamental_cm – Fundamental vibration wavenumber in cm⁻¹.
max_order – Maximum overtone order to consider.
wavelength_range – NIR wavelength range in nm (min, max).
anharmonicity – Anharmonicity constant.
- Returns:
List of OvertoneResult objects for bands within the NIR range.
Example
>>> # Get NIR overtones of O-H stretch >>> overtones = get_nir_overtones_for_fundamental(3400) >>> for ot in overtones: ... print(ot)
- nirs4all.synthesis.wavenumber.get_zone_wavelength_range(zone_name: str) Tuple[float, float] | None[source]
Get the wavelength range (nm) for a named NIR zone.
- Parameters:
zone_name – Name of the NIR zone (e.g., ‘1st_overtones_OH_NH’).
- Returns:
Tuple of (min_wavelength, max_wavelength) in nm, or None if not found.
Example
>>> get_zone_wavelength_range('1st_overtones_CH') (1600.0, 1818.18...)
- nirs4all.synthesis.wavenumber.is_nir_region(wavelength_nm: float) bool[source]
Check if a wavelength is in the NIR region (700-2500 nm).
- Parameters:
wavelength_nm – Wavelength in nm.
- Returns:
True if wavelength is in NIR region.
Example
>>> is_nir_region(1450) True >>> is_nir_region(500) False
- nirs4all.synthesis.wavenumber.is_visible_region(wavelength_nm: float) bool[source]
Check if a wavelength is in the visible region (350-700 nm).
- Parameters:
wavelength_nm – Wavelength in nm.
- Returns:
True if wavelength is in visible region.
Example
>>> is_visible_region(500) True >>> is_visible_region(1450) False
- nirs4all.synthesis.wavenumber.wavelength_to_wavenumber(lambda_nm: float | ndarray) float | ndarray[source]
Convert wavelength (nm) to wavenumber (cm⁻¹).
The conversion follows the relationship: ν̃ = 10^7 / λ
- Parameters:
lambda_nm – Wavelength in nm. Can be a scalar or numpy array.
- Returns:
Wavenumber in cm⁻¹ (same shape as input).
- Raises:
ValueError – If wavelength is zero or negative.
Example
>>> wavelength_to_wavenumber(1450) # O-H 1st overtone region 6896.55... >>> wavelength_to_wavenumber(np.array([1450, 1940])) array([6896.55..., 5154.64...])
- nirs4all.synthesis.wavenumber.wavenumber_to_wavelength(nu_cm: float | ndarray) float | ndarray[source]
Convert wavenumber (cm⁻¹) to wavelength (nm).
The conversion follows the relationship: λ = 10^7 / ν̃
- Parameters:
nu_cm – Wavenumber in cm⁻¹. Can be a scalar or numpy array.
- Returns:
Wavelength in nm (same shape as input).
- Raises:
ValueError – If wavenumber is zero or negative.
Example
>>> wavenumber_to_wavelength(6896) # 1st overtone O-H 1450.26... >>> wavenumber_to_wavelength(np.array([6896, 5155])) array([1450.26..., 1939.88...])