Source code for epyr.physics.conversions

"""
Direct conversion functions for EPR spectroscopy

Provides simple, direct conversion functions between common EPR units:
MHz, mT, cm-1, and related energy/field conversions.
"""

from typing import List, Optional, Union

import numpy as np

from ..logging_config import get_logger
from .constants import bmagn, boltzm, clight, evolt, gfree, planck

logger = get_logger(__name__)


[docs] def mhz_to_mt( frequency_mhz: Union[float, np.ndarray], g_factor: Optional[Union[float, np.ndarray]] = None, ) -> Union[float, np.ndarray]: """ Convert frequency in MHz to magnetic field in mT. Uses the fundamental EPR relation: B = h*nu / (g*mu_B) Parameters: ----------- frequency_mhz : float or array Frequency in MHz g_factor : float or array, optional g-factor (defaults to free electron g-factor = 2.002319...) Returns: -------- float or array Magnetic field in mT Examples: --------- >>> # X-band EPR frequency >>> field = mhz_to_mt(9500) # 9.5 GHz >>> print(f"Field: {field:.1f} mT") >>> # Different g-factors >>> fields = mhz_to_mt(9500, g_factor=[2.000, 2.005, 2.010]) >>> print(f"Fields: {fields}") """ # Store original input types for return type determination freq_is_scalar = np.isscalar(frequency_mhz) g_is_scalar = g_factor is None or np.isscalar(g_factor) if g_factor is None: g_factor = gfree() # Convert MHz to Hz, then use fundamental relation frequency_hz = np.asarray(frequency_mhz) * 1e6 g_factor_arr = np.asarray(g_factor) field_tesla = frequency_hz * planck() / (g_factor_arr * bmagn()) field_mt = field_tesla * 1000 # Tesla to mT # Return scalar if both inputs were scalar if freq_is_scalar and g_is_scalar: return float(field_mt) return field_mt
[docs] def mt_to_mhz( field_mt: Union[float, np.ndarray], g_factor: Optional[Union[float, np.ndarray]] = None, ) -> Union[float, np.ndarray]: """ Convert magnetic field in mT to frequency in MHz. Uses the fundamental EPR relation: nu = g*mu_B*B / h Parameters: ----------- field_mt : float or array Magnetic field in mT g_factor : float or array, optional g-factor (defaults to free electron g-factor = 2.002319...) Returns: -------- float or array Frequency in MHz Examples: --------- >>> # What frequency for 340 mT field? >>> freq = mt_to_mhz(340) >>> print(f"Frequency: {freq:.1f} MHz") >>> # Array of fields >>> freqs = mt_to_mhz([100, 200, 300, 400]) >>> print(f"Frequencies: {freqs}") """ # Store original input types for return type determination field_is_scalar = np.isscalar(field_mt) g_is_scalar = g_factor is None or np.isscalar(g_factor) if g_factor is None: g_factor = gfree() # Convert mT to Tesla, then use fundamental relation field_tesla = np.asarray(field_mt) * 1e-3 g_factor_arr = np.asarray(g_factor) frequency_hz = g_factor_arr * bmagn() * field_tesla / planck() frequency_mhz = frequency_hz / 1e6 # Hz to MHz # Return scalar if both inputs were scalar if field_is_scalar and g_is_scalar: return float(frequency_mhz) return frequency_mhz
[docs] def cm_inv_to_mhz( wavenumber_cm_inv: Union[float, np.ndarray], ) -> Union[float, np.ndarray]: """ Convert wavenumber in cm^-1 to frequency in MHz. Uses the relation: nu = c * wavenumber where c is the speed of light. Parameters: ----------- wavenumber_cm_inv : float or array Wavenumber in cm^-1 Returns: -------- float or array Frequency in MHz Examples: --------- >>> # Convert 1000 cm^-1 to MHz >>> freq = cm_inv_to_mhz(1000) >>> print(f"Frequency: {freq:.3e} MHz") >>> # Array conversion >>> freqs = cm_inv_to_mhz([100, 500, 1000, 2000]) >>> print(f"Frequencies: {freqs}") """ # wavenumber in cm^-1 * c in m/s * 100 (cm->m) / 1e6 (Hz->MHz) wavenumber_cm_inv = np.asarray(wavenumber_cm_inv) frequency_mhz = wavenumber_cm_inv * clight() * 100 / 1e6 # Return scalar if input was scalar if np.isscalar(wavenumber_cm_inv): return float(frequency_mhz) return frequency_mhz
[docs] def mhz_to_cm_inv(frequency_mhz: Union[float, np.ndarray]) -> Union[float, np.ndarray]: """ Convert frequency in MHz to wavenumber in cm^-1. Uses the relation: wavenumber = nu / c where c is the speed of light. Parameters: ----------- frequency_mhz : float or array Frequency in MHz Returns: -------- float or array Wavenumber in cm^-1 Examples: --------- >>> # Convert 30000 MHz (30 GHz) to cm^-1 >>> wn = mhz_to_cm_inv(30000) >>> print(f"Wavenumber: {wn:.3f} cm^-1") >>> # Array conversion >>> wns = mhz_to_cm_inv([1000, 5000, 10000, 30000]) >>> print(f"Wavenumbers: {wns}") """ # frequency in MHz * 1e6 (MHz->Hz) / (c in m/s * 100 (m->cm)) frequency_mhz = np.asarray(frequency_mhz) wavenumber_cm_inv = frequency_mhz * 1e6 / (clight() * 100) # Return scalar if input was scalar if np.isscalar(frequency_mhz): return float(wavenumber_cm_inv) return wavenumber_cm_inv
[docs] def frequency_field_conversion_table( frequencies_ghz: Optional[List[float]] = None, g_factors: Optional[List[float]] = None, ) -> None: """Log a frequency-vs-field conversion table for common EPR bands. Parameters ---------- frequencies_ghz : list of float, optional Microwave frequencies in GHz. Default ``[1, 3, 9.5, 34, 95, 263]`` (L, S, X, Q, W, J bands). g_factors : list of float, optional Electron g-factors to use for the conversion. Default ``[2.000, 2.002, 2.005, 2.010]``. Returns ------- None Output goes through the module logger; nothing is returned. Examples -------- >>> from epyr.physics import frequency_field_conversion_table >>> frequency_field_conversion_table(frequencies_ghz=[9.5]) # X-band only """ if frequencies_ghz is None: frequencies_ghz = [1, 3, 9.5, 34, 95, 263] # Common EPR bands if g_factors is None: g_factors = [2.000, 2.002, 2.005, 2.010] logger.info("EPR Frequency to Magnetic Field Conversion Table") logger.info("=" * 60) header = f"{'Freq (GHz)':<12}" for g in g_factors: header += f"{'g=' + str(g):<12}" logger.info(header) logger.info("-" * 60) for freq_ghz in frequencies_ghz: freq_mhz = freq_ghz * 1000 row = f"{freq_ghz:<12.1f}" for g in g_factors: field_mt = mhz_to_mt(freq_mhz, g_factor=g) row += f"{field_mt:<12.1f}" logger.info(row) logger.info("-" * 60) logger.info("All fields in mT")
[docs] def energy_conversion_table(energies_cm_inv: Optional[List[float]] = None) -> None: """Log an energy conversion table covering cm⁻¹, MHz, GHz, eV, K, meV. Parameters ---------- energies_cm_inv : list of float, optional Wavenumbers in cm⁻¹. Default ``[1, 10, 100, 1000, 5000, 10000]``, covering the typical EPR / zero-field-splitting range. Returns ------- None Examples -------- >>> from epyr.physics import energy_conversion_table >>> energy_conversion_table(energies_cm_inv=[1, 1000]) """ if energies_cm_inv is None: energies_cm_inv = [1, 10, 100, 1000, 5000, 10000] logger.info("Energy Unit Conversion Table") logger.info("=" * 70) logger.info( f"{'cm^-1':<10} {'MHz':<15} {'GHz':<10} {'eV':<12} {'K':<10} {'meV':<8}" ) logger.info("-" * 70) for wn in energies_cm_inv: # Convert cm^-1 to other units freq_mhz = cm_inv_to_mhz(wn) freq_ghz = freq_mhz / 1000 # Using physical constants for other conversions energy_j = wn * 100 * clight() * planck() # cm^-1 to J energy_ev = energy_j / evolt() # J to eV energy_mev = energy_ev * 1000 # eV to meV temp_k = energy_j / boltzm() # J to K logger.info( f"{wn:<10.0f} {freq_mhz:<15.3e} {freq_ghz:<10.3f} " f"{energy_ev:<12.6f} {temp_k:<10.3f} {energy_mev:<8.3f}" ) logger.info("-" * 70)
[docs] def conversion_examples(): """Show practical examples of EPR unit conversions.""" logger.info("EPR Unit Conversion Examples") logger.info("=" * 40) # Example 1: X-band EPR logger.info("1. X-band EPR (9.5 GHz)") freq_xband = 9500 # MHz field_xband = mhz_to_mt(freq_xband) logger.info(f" Frequency: {freq_xband} MHz") logger.info(f" Field: {field_xband:.1f} mT") logger.info(f" Check: {mt_to_mhz(field_xband):.0f} MHz") logger.info("") # Example 2: Q-band EPR logger.info("2. Q-band EPR (~34 GHz)") freq_qband = 34000 # MHz field_qband = mhz_to_mt(freq_qband) logger.info(f" Frequency: {freq_qband} MHz") logger.info(f" Field: {field_qband:.0f} mT") logger.info("") # Example 3: Energy scale conversions logger.info("3. Energy scale conversions") energy_wn = 1000 # cm^-1 energy_freq = cm_inv_to_mhz(energy_wn) logger.info(f" {energy_wn} cm^-1 = {energy_freq:.3e} MHz") logger.info(f" Check: {mhz_to_cm_inv(energy_freq):.1f} cm^-1") logger.info("") # Example 4: Different g-factors logger.info("4. g-factor effects at X-band") freq = 9500 # MHz g_values = [2.000, 2.002, 2.005, 2.010] fields = mhz_to_mt(freq, g_factor=g_values) logger.info(f" Frequency: {freq} MHz") for g, b in zip(g_values, fields): logger.info(f" g = {g:.3f}: {b:.1f} mT") logger.info("") # Example 5: Field sweep range logger.info("5. Typical EPR field sweep (g = 2.002)") center_field = 340 # mT sweep_width = 20 # mT fields = np.array( [center_field - sweep_width / 2, center_field, center_field + sweep_width / 2] ) freqs = mt_to_mhz(fields, g_factor=2.002) logger.info(f" Field range: {fields[0]:.1f} - {fields[2]:.1f} mT") logger.info(f" Frequency range: {freqs[0]:.1f} - {freqs[2]:.1f} MHz") logger.info(f" Frequency width: {freqs[2] - freqs[0]:.1f} MHz")
if __name__ == "__main__": conversion_examples() logger.info("") frequency_field_conversion_table() logger.info("") energy_conversion_table()