rf-touchstone
Version:
A Javascript/TypeScript library for reading, manipulating, and writing Touchstone files (.snp files) used in radio frequency (RF) and microwave engineering.
524 lines (470 loc) • 14.8 kB
text/typescript
/**
* Supported frequency units in the Touchstone specification.
* Note: THz is not officially supported as a file header unit in Touchstone v1.x/v2.x.
*
* @remarks
* While THz can be accessed programmatically via the `f_THz` getter/setter for unit conversion,
* it cannot be used as the `unit` property value. Attempting to set `frequency.unit = 'THz'`
* will throw an error.
*/
export const FrequencyUnits = ['Hz', 'kHz', 'MHz', 'GHz'] as const
/**
* Type representing the frequency unit.
* - Hz: Hertz ($10^0$ Hz)
* - kHz: Kilohertz ($10^3$ Hz)
* - MHz: Megahertz ($10^6$ Hz)
* - GHz: Gigahertz ($10^9$ Hz)
*/
export type FrequencyUnit = (typeof FrequencyUnits)[number]
/**
* Speed of light in m/s
*/
export const SPEED_OF_LIGHT = 299792458
/**
* Multipliers for converting from any FrequencyUnit or THz to Hz
*/
export const FREQUENCY_MULTIPLIERS: Record<FrequencyUnit | 'THz', number> = {
Hz: 1,
kHz: 1e3,
MHz: 1e6,
GHz: 1e9,
THz: 1e12,
}
/**
* Multipliers for converting from other wavelength units to meters
*/
export const WAVELENGTH_MULTIPLIERS_TO_M: Record<string, number> = {
m: 1,
cm: 1e-2,
mm: 1e-3,
um: 1e-6,
nm: 1e-9,
}
/**
* Represents frequency data and provides utility methods for unit conversion and wavelength calculation.
*
* @remarks
* The `Frequency` class is designed to handle frequency points commonly used in RF, microwave,
* and high-speed digital engineering. It maintains an internal unit and a set of frequency points,
* allowing for seamless conversion between different frequency and wavelength units.
*
* ##### Key Features:
* - **Unit Awareness**: Keeps track of whether frequencies are defined in Hz, MHz, GHz, etc.
* - **Automatic Conversion**: Automatically scales frequency points when the `unit` property is changed.
* - **Wavelength Utilities**: Provides getters and setters for wavelength ($\lambda = c/f$) in various metric units.
*
* @example
* #### Basic Usage
* ```typescript
* import { Frequency } from 'rf-touchstone';
*
* const freq = new Frequency();
* freq.unit = 'GHz';
* freq.f_scaled = [1.0, 2.0, 5.0];
*
* console.log(freq.f_Hz); // [1e9, 2e9, 5e9]
* console.log(freq.wavelength_mm); // [299.79, 149.90, 59.96]
* ```
*/
export class Frequency {
/**
* Internal storage for frequency unit
* Defaults to 'Hz' as the base SI unit for frequency
*/
private _unit: FrequencyUnit = 'Hz'
/**
* Sets the frequency unit. If frequency points already exist, they will be
* automatically rescaled to the new unit.
*
* @param newUnit - The target frequency unit (Hz, kHz, MHz, or GHz).
* @throws Error if the provided unit is invalid.
*/
set unit(newUnit: FrequencyUnit) {
if (typeof newUnit !== 'string') {
throw new Error(`Unknown frequency unit: ${newUnit}`)
}
const oldUnit = this._unit // Store the old unit
// Validate and parse the new unit
let parsedUnit: FrequencyUnit
switch (newUnit.toLowerCase()) {
case 'hz':
parsedUnit = 'Hz'
break
case 'khz':
parsedUnit = 'kHz'
break
case 'mhz':
parsedUnit = 'MHz'
break
case 'ghz':
parsedUnit = 'GHz'
break
default:
throw new Error(`Unknown frequency unit: ${newUnit}`)
}
// If the unit is actually changing and f_scaled is populated
if (parsedUnit !== oldUnit && this.f_scaled && this.f_scaled.length > 0) {
const oldMultiplier = FREQUENCY_MULTIPLIERS[oldUnit]
const newMultiplier = FREQUENCY_MULTIPLIERS[parsedUnit]
/* v8 ignore start */
if (oldMultiplier && newMultiplier) {
// Ensure multipliers are found
this.f_scaled = this.f_scaled.map(
(freq) => (freq * oldMultiplier) / newMultiplier
)
} else {
// This case should ideally not happen if units are validated correctly
throw new Error(
`Could not find frequency multipliers (old: ${oldMultiplier}, new: ${newMultiplier}) for unit conversion`
)
}
/* v8 ignore stop */
}
this._unit = parsedUnit // Update the internal unit
}
/**
* Gets the current frequency unit.
*/
get unit(): FrequencyUnit {
return this._unit
}
/**
* Internal storage for frequency points array
* Each element represents a frequency point in the specified unit
* @private
*/
private _f_scaled: number[] = []
/**
* Sets the array of frequency points in the current frequency unit.
*
* @param value - Array of numerical frequency points.
* @throws Error if the input is not a non-negative number array.
*/
set f_scaled(value: number[]) {
// Validate input is an array
if (!Array.isArray(value)) {
throw new Error('Frequency value must be an array')
}
// Validate all elements are numbers and non-negative
for (const val of value) {
if (typeof val !== 'number') {
throw new Error(
`Frequency value must be an array of numbers, but received: ${val}`
)
}
if (val < 0) {
throw new Error(
`Frequency values cannot be negative, but received: ${val}`
)
}
}
// Store the validated frequency points
this._f_scaled = value
}
/**
* Gets the array of frequency points in the current unit.
*/
get f_scaled(): number[] {
return this._f_scaled
}
/**
* Private helper method to get frequency values in a target unit.
* @param targetUnit - The key of the target frequency unit in FREQUENCY_MULTIPLIERS.
* @returns Array of frequency points in the target unit.
*/
private _getFrequencyInTargetUnit(
targetUnit: keyof typeof FREQUENCY_MULTIPLIERS
): number[] {
if (!this.f_scaled || this.f_scaled.length === 0) {
return []
}
const currentUnitMultiplier = FREQUENCY_MULTIPLIERS[this.unit]
/* v8 ignore start */
if (!currentUnitMultiplier) {
throw new Error(`Multiplier for current unit ${this.unit} not found.`)
}
/* v8 ignore stop */
const targetMultiplier = FREQUENCY_MULTIPLIERS[targetUnit]
/* v8 ignore start */
if (!targetMultiplier) {
throw new Error(`Multiplier for target unit ${targetUnit} not found.`)
}
/* v8 ignore stop */
return this.f_scaled.map(
(val) => (val * currentUnitMultiplier) / targetMultiplier
)
}
/**
* Internal helper to set frequency values from a source unit.
* @param values - Array of frequency points.
* @param sourceUnit - The source frequency unit key.
*/
private _setFrequencyFromTargetUnit(
values: number[],
sourceUnit: keyof typeof FREQUENCY_MULTIPLIERS
): void {
/* v8 ignore start */
if (!values) {
// Handle null or undefined input array
this.f_scaled = []
return
}
/* v8 ignore stop */
if (values.length === 0) {
/* v8 ignore start */
this.f_scaled = []
return
}
/* v8 ignore stop */
const sourceMultiplier = FREQUENCY_MULTIPLIERS[sourceUnit]
/* v8 ignore start */
if (!sourceMultiplier) {
throw new Error(`Multiplier for source unit ${sourceUnit} not found.`)
}
/* v8 ignore stop */
const currentFreqMultiplier = FREQUENCY_MULTIPLIERS[this.unit]
/* v8 ignore start */
if (!currentFreqMultiplier) {
throw new Error(
`Multiplier for current internal unit ${this.unit} not found.`
)
}
/* v8 ignore stop */
const convertedValues = values.map(
(val) => (val * sourceMultiplier) / currentFreqMultiplier
)
this.f_scaled = convertedValues
}
/**
* Gets the frequency points in Hertz (Hz).
*/
get f_Hz(): number[] {
return this._getFrequencyInTargetUnit('Hz')
}
/**
* Sets the frequency points in Hertz (Hz).
*/
set f_Hz(values: number[]) {
this._setFrequencyFromTargetUnit(values, 'Hz')
}
/**
* Gets the frequency points in Kilohertz (kHz).
*/
get f_kHz(): number[] {
return this._getFrequencyInTargetUnit('kHz')
}
/**
* Sets the frequency points in Kilohertz (kHz).
*/
set f_kHz(values: number[]) {
this._setFrequencyFromTargetUnit(values, 'kHz')
}
/**
* Gets the frequency points in Megahertz (MHz).
*/
get f_MHz(): number[] {
return this._getFrequencyInTargetUnit('MHz')
}
/**
* Sets the frequency points in Megahertz (MHz).
*/
set f_MHz(values: number[]) {
this._setFrequencyFromTargetUnit(values, 'MHz')
}
/**
* Gets the frequency points in Gigahertz (GHz).
*/
get f_GHz(): number[] {
return this._getFrequencyInTargetUnit('GHz')
}
/**
* Sets the frequency points in Gigahertz (GHz).
*/
set f_GHz(values: number[]) {
this._setFrequencyFromTargetUnit(values, 'GHz')
}
/**
* Gets the frequency points in Terahertz (THz).
*
* @remarks
* THz is available for programmatic unit conversion but is NOT a valid file format unit.
* Use this getter to convert existing frequency data to THz for calculations or display.
*/
get f_THz(): number[] {
return this._getFrequencyInTargetUnit('THz')
}
/**
* Sets the frequency points in Terahertz (THz).
*
* @remarks
* THz is available for programmatic unit conversion but is NOT a valid file format unit.
* This setter converts THz values to the current internal unit (set via `unit` property).
* Do not attempt to set `unit = 'THz'` as it will throw an error.
*/
set f_THz(values: number[]) {
this._setFrequencyFromTargetUnit(values, 'THz')
}
/**
* Private helper method to get wavelength values in a target unit.
* @param targetWavelengthUnit - The key of the target wavelength unit in WAVELENGTH_MULTIPLIERS_TO_M.
* @returns Array of wavelength points in the target unit.
*/
private _getWavelengthInTargetUnit(
targetWavelengthUnit: keyof typeof WAVELENGTH_MULTIPLIERS_TO_M
): number[] {
if (!this.f_scaled || this.f_scaled.length === 0) {
return []
}
const currentFreqUnitMultiplier = FREQUENCY_MULTIPLIERS[this.unit]
/* v8 ignore start */
if (!currentFreqUnitMultiplier) {
throw new Error(
`Frequency multiplier for current unit ${this.unit} not found.`
)
}
/* v8 ignore stop */
const targetWavelengthToMMultiplier =
WAVELENGTH_MULTIPLIERS_TO_M[targetWavelengthUnit]
/* v8 ignore start */
if (!targetWavelengthToMMultiplier) {
throw new Error(
`Wavelength multiplier to meters for target wavelength unit ${targetWavelengthUnit} not found.`
)
}
/* v8 ignore stop */
return this.f_scaled.map((val) => {
const freqInHz = val * currentFreqUnitMultiplier
if (freqInHz === 0) {
return Infinity
}
const wavelengthInMeters = SPEED_OF_LIGHT / freqInHz
return wavelengthInMeters / targetWavelengthToMMultiplier
})
}
/**
* Private helper method to set frequency values from wavelength values in a source unit.
* @param values - Array of wavelength points in the source unit.
* @param sourceWavelengthUnit - The key of the source wavelength unit in WAVELENGTH_MULTIPLIERS_TO_M.
*/
private _setWavelengthFromTargetUnit(
values: number[],
sourceWavelengthUnit: keyof typeof WAVELENGTH_MULTIPLIERS_TO_M
): void {
/* v8 ignore start */
if (!values) {
// Handle null or undefined input array
this.f_scaled = []
return
}
/* v8 ignore stop */
if (values.length === 0) {
this.f_scaled = []
return
}
const sourceWavelengthToMMultiplier =
WAVELENGTH_MULTIPLIERS_TO_M[sourceWavelengthUnit]
/* v8 ignore start */
if (!sourceWavelengthToMMultiplier) {
throw new Error(
`Wavelength multiplier to meters for source unit ${sourceWavelengthUnit} not found.`
)
}
/* v8 ignore stop */
const currentFreqUnitMultiplier = FREQUENCY_MULTIPLIERS[this.unit]
/* v8 ignore start */
if (!currentFreqUnitMultiplier) {
throw new Error(
`Frequency multiplier for current unit ${this.unit} not found.`
)
}
/* v8 ignore stop */
const convertedFrequencies = values.map((val) => {
const wavelengthInMeters = val * sourceWavelengthToMMultiplier
if (wavelengthInMeters === 0) {
throw new Error('Cannot convert zero wavelength to frequency.')
}
const freqInHz = SPEED_OF_LIGHT / wavelengthInMeters
return freqInHz / currentFreqUnitMultiplier
})
this.f_scaled = convertedFrequencies
}
/**
* Gets the wavelength in meters (m).
*/
get wavelength_m(): number[] {
return this._getWavelengthInTargetUnit('m')
}
/**
* Sets the wavelength in meters (m).
*
* @remarks
* This is a bidirectional conversion: setting wavelength automatically calculates and updates
* the underlying frequency points using the relationship f = c/λ, where c is the speed of light.
* The resulting frequencies are stored in the current unit specified by the `unit` property.
*
* @throws Error if any wavelength value is zero (division by zero).
*/
set wavelength_m(values: number[]) {
this._setWavelengthFromTargetUnit(values, 'm')
}
/**
* Gets the wavelength in centimeters (cm).
*/
get wavelength_cm(): number[] {
return this._getWavelengthInTargetUnit('cm')
}
/**
* Sets the wavelength in centimeters (cm).
*
* @remarks
* Bidirectional conversion: Updates frequency points via f = c/λ.
*/
set wavelength_cm(values: number[]) {
this._setWavelengthFromTargetUnit(values, 'cm')
}
/**
* Gets the wavelength in millimeters (mm).
*/
get wavelength_mm(): number[] {
return this._getWavelengthInTargetUnit('mm')
}
/**
* Sets the wavelength in millimeters (mm).
*
* @remarks
* Bidirectional conversion: Updates frequency points via f = c/λ.
*/
set wavelength_mm(values: number[]) {
this._setWavelengthFromTargetUnit(values, 'mm')
}
/**
* Gets the wavelength in micrometers (μm).
*/
get wavelength_um(): number[] {
return this._getWavelengthInTargetUnit('um')
}
/**
* Sets the wavelength in micrometers (μm).
*
* @remarks
* Bidirectional conversion: Updates frequency points via f = c/λ.
*/
set wavelength_um(values: number[]) {
this._setWavelengthFromTargetUnit(values, 'um')
}
/**
* Gets the wavelength in nanometers (nm).
*/
get wavelength_nm(): number[] {
return this._getWavelengthInTargetUnit('nm')
}
/**
* Sets the wavelength in nanometers (nm).
*
* @remarks
* Bidirectional conversion: Updates frequency points via f = c/λ.
*/
set wavelength_nm(values: number[]) {
this._setWavelengthFromTargetUnit(values, 'nm')
}
}