Source code for touchstone.parser.utilities.touchstone_writer

"""
Utilities for writing Touchstone data back to the .sNp file format.

This module provides :func:`write_snp` and :func:`write_snp_to_string`
for exporting :class:`~touchstone.parser.models.touchstone_data.TouchstoneData`
back to the standard Touchstone text format with round-trip fidelity.
"""

import io
from typing import Any, List, Optional

import numpy as np

from ..models.data_format import DataFormat
from ..models.frequency_unit import FrequencyUnit
from ..models.touchstone_data import TouchstoneData
from ..models.touchstone_options import TouchstoneOptions


def _format_value(value: Any) -> str:
    """Format a numeric value for Touchstone output.

    Uses general format with up to 10 significant digits to maintain
    precision while keeping output compact.

    Args:
        value: The numeric value to format.

    Returns:
        str: The formatted string representation.
    """
    return f"{value:.10g}"


def _append_parameter(parts: List[str], s: complex, fmt: DataFormat):
    """Append a formatted complex parameter to the output parts list.

    Converts the complex value to the specified data format (RI, MA, or DB)
    and appends the two resulting numeric strings to ``parts``.

    Args:
        parts: The list of string parts to append to.
        s: The complex S-parameter value.
        fmt: The :class:`DataFormat` specifying the output representation.
    """
    if fmt == DataFormat.RI:
        parts.append(_format_value(s.real))
        parts.append(_format_value(s.imag))
    elif fmt == DataFormat.MA:
        mag = np.abs(s)
        phase = np.angle(s, deg=True)
        parts.append(_format_value(mag))
        parts.append(_format_value(phase))
    elif fmt == DataFormat.DB:
        mag = np.abs(s)
        mag_db = 20 * np.log10(mag) if mag > 1e-20 else -400.0
        phase = np.angle(s, deg=True)
        parts.append(_format_value(mag_db))
        parts.append(_format_value(phase))


[docs] def write_snp( data: TouchstoneData, filepath: str, options: Optional[TouchstoneOptions] = None ) -> None: """Write Touchstone data to a file. Args: data: The :class:`TouchstoneData` to write. filepath: The destination file path (e.g., ``'output.s2p'``). options: Optional :class:`TouchstoneOptions` to override the export format. If ``None``, uses ``data.options``. Example: >>> write_snp(data, "output.s2p") """ content = write_snp_to_string(data, options) with open(filepath, "w", encoding="utf-8") as f: f.write(content)
[docs] def write_snp_to_string( data: TouchstoneData, options: Optional[TouchstoneOptions] = None ) -> str: """Convert Touchstone data to its string representation. Produces a valid Touchstone file content string including comments, the option line, and all data rows. Args: data: The :class:`TouchstoneData` to convert. options: Optional :class:`TouchstoneOptions` to override the export format. If ``None``, uses ``data.options``. Returns: str: The raw Touchstone file content as a string. Example: >>> content = write_snp_to_string(data) >>> print(content[:50]) # GHZ S MA R 50.0 """ if options is None: options = data.options output = io.StringIO() for comment in data.comments: output.write(f"! {comment}\n") output.write(f"{options}\n") freq_divisors = { FrequencyUnit.HZ: 1.0, FrequencyUnit.KHZ: 1e3, FrequencyUnit.MHZ: 1e6, FrequencyUnit.GHZ: 1e9, } freq_div = freq_divisors.get(options.frequency_unit, 1.0) n = data.n_ports for p in range(data.n_freq): freq = data.frequency[p] / freq_div if n <= 2: parts = [_format_value(freq)] if n == 1: _append_parameter( parts, data.s_parameters[p, 0, 0], options.data_format ) else: _append_parameter( parts, data.s_parameters[p, 0, 0], options.data_format ) _append_parameter( parts, data.s_parameters[p, 1, 0], options.data_format ) _append_parameter( parts, data.s_parameters[p, 0, 1], options.data_format ) _append_parameter( parts, data.s_parameters[p, 1, 1], options.data_format ) output.write(" ".join(parts) + "\n") else: first_row = True for row in range(n): parts = [] if first_row: parts.append(_format_value(freq)) first_row = False for col in range(n): _append_parameter( parts, data.s_parameters[p, row, col], options.data_format ) output.write(" ".join(parts) + "\n") return output.getvalue()