Touchstone (SNP) parser for RF engineering with N-port network analysis
Parse, analyze, and manipulate Touchstone files with any number of ports (1-port, 2-port, 3-port, 4-port, and beyond).
See the support table for separate parsing, writing, plotting, and numerical contracts, including supported file versions and metadata limits.
Use touchstone when the task starts from measured or simulated S-parameter
data: .s1p, .s2p, .sNp parsing/writing, plotting, resampling,
reference-impedance metadata, S/Y/Z/ABCD conversion, or two-port network
cascading.
If the task is scalar RF unit math, use rfconversions. If it is a block-level
gain/NF/P1dB/IP3 lineup, use gainlineup. If it is an end-to-end radio link
question involving EIRP, path loss, C/No, Eb/No, BER, margin, orbit, Doppler,
PFD, or modulation, use linkbudget.
S-parameter port indices are 1-indexed: s_db(2, 1) is S21. Parsed frequencies
are stored in Hz, and interpolation is performed in real/imaginary space before
rebuilding magnitude/angle or dB/angle views.
cargo install touchstoneThis installs an executable in your .cargo/bin directory (.cargo/bin/touchstone).
Touchstone files (also called SNP files) are the industry-standard format for storing S-parameter data measured or simulated for RF and microwave networks.
Each file describes how electromagnetic signals scatter through an N-port network — reflections, transmissions, and coupling — across a range of frequencies.
The file extension encodes the port count: .s1p for a 1-port, .s2p for a 2-port, .s3p
for a 3-port, and so on up to .s32p and beyond.
A typical .s2p file looks like this:
! Two-port network measurement
# GHz S RI R 50
1.0 0.5 -0.3 0.1 0.2 0.1 0.2 0.5 -0.3
2.0 0.4 -0.2 0.2 0.1 0.2 0.1 0.4 -0.2
The # line is the option line: it declares the frequency unit (GHz), parameter type (S),
data format (RI = Real-Imaginary), and reference impedance (R 50 = 50 Ω).
Use Network::new to parse any Touchstone file:
use touchstone::Network;
fn main() -> Result<(), touchstone::TouchstoneError> {
let ntwk = Network::new("files/ntwk1.s2p")?;
println!("Ports: {}", ntwk.rank);
println!("Frequency unit: {}", ntwk.frequency_unit);
println!("Format: {}", ntwk.format);
println!("Reference impedance: {} Ω", ntwk.z0);
println!("Data points: {}", ntwk.frequencies().len());
Ok(())
}Network::new auto-detects the port count, data format, and frequency unit from the file, and
returns I/O or parse errors instead of panicking.
For uploaded data or API endpoints, parse Touchstone content directly from memory. The
source_name argument is used as the network name and for .sNp extension inference:
use touchstone::Network;
fn main() -> Result<(), touchstone::TouchstoneError> {
let body = b"# GHz S RI R 50\n1.0 0.1 0.0 4.0 0.0 0.01 0.0 0.2 0.0\n";
let ntwk = Network::from_bytes("uploaded.s2p", body)?;
assert_eq!(ntwk.rank, 2);
Ok(())
}Non-fatal parser diagnostics are stored in network.warnings:
use touchstone::{Network, TouchstoneWarning};
fn main() -> Result<(), touchstone::TouchstoneError> {
let ntwk = Network::from_str("uploaded.s1p", "1.0 0.5 0.0\n")?;
assert!(matches!(
ntwk.warnings.as_slice(),
[TouchstoneWarning::MissingOptionLine { .. }]
));
Ok(())
}Touchstone v2 reference impedance metadata is available through
network.reference_impedance(). Networks with one scalar reference impedance return
ReferenceImpedance::Common(z0), while files with per-port [Reference] values return
ReferenceImpedance::PerPort(values).
S-parameters are accessed with 1-indexed port numbers, matching the conventional S₁₁, S₂₁, etc. notation used in RF engineering.
Three accessor methods return a Vec over all frequencies:
| Method | Returns | Struct fields |
|---|---|---|
s_db |
dB magnitude + angle (°) | FrequencyDB { frequency, s_db } |
s_ri |
Real + imaginary parts | FrequencyRI { frequency, s_ri } |
s_ma |
Linear magnitude + angle | FrequencyMA { frequency, s_ma } |
The network's cached RI/MA/DB matrices are private. These accessors return owned
values. Changes to those values do not change the network or its serialized data.
Use NetworkBuilder to construct a network from generated values.
Migration from direct network.s access: use s_ri, s_ma, or s_db for a
trace, s_matrix_at for one matrix, and point_at or points for frequency/matrix
pairs. Direct reads and writes through network.s no longer compile.
use touchstone::Network;
let ntwk = Network::new("files/ntwk1.s2p")?;
// S11 in dB (return loss)
let s11_db = ntwk.s_db(1, 1);
for point in &s11_db {
println!("f={} : dB={}, angle={}", point.frequency, point.s_db.decibel(), point.s_db.angle());
}
// S21 in Real-Imaginary
let s21_ri = ntwk.s_ri(2, 1);
for point in &s21_ri {
println!("f={} : re={}, im={}", point.frequency, point.s_ri.real(), point.s_ri.imaginary());
}
// S21 in Magnitude-Angle
let s21_ma = ntwk.s_ma(2, 1);
for point in &s21_ma {
println!("f={} : mag={}, angle={}", point.frequency, point.s_ma.magnitude(), point.s_ma.angle());
}For matrix-oriented workflows, use s_matrix_at(point_index) to get a stable full S-parameter
matrix for one frequency point. NetworkPoint is returned by sample_at, and also exposes the
full SMatrix at the requested frequency.
For reads without copying matrices, use frequencies(), point_ref(index), or
iter_points(). The stored frequency grid is private. f() still returns an
owned copy. Borrowed points expose frequency() and a read-only s() view:
use touchstone::{Complex, NetworkBuilder, SMatrix};
fn main() -> Result<(), touchstone::TouchstoneError> {
let matrix = SMatrix::try_new(vec![vec![Complex { re: 0.5, im: 0.0 }]])?;
let network = NetworkBuilder::new("generated.s1p", 1)
.point(0.0, matrix.clone())
.point(1.0e9, matrix)
.build()?;
for point in network.iter_points() {
println!("{} Hz: {:?}", point.frequency(), point.s().get(1, 1)?);
}
Ok(())
}Use try_new on SMatrix, ParameterMatrix, ABCDMatrix, and NetworkPoint
to validate new values. Generated networks and sampling require finite,
nonnegative, strictly increasing frequencies. Duplicate frequencies are rejected.
DC (0 Hz) is supported.
sample_at(frequency_hz, interpolation, extrapolation) samples a network at one frequency.
resample(frequencies_hz, interpolation, extrapolation) returns a new Network on a requested
frequency grid. Linear interpolation is performed in real/imaginary space, with magnitude/angle and
dB/angle values rebuilt from the interpolated complex values.
| Item | Description |
|---|---|
Interpolation::Linear |
Linear interpolation of each real and imaginary component |
Interpolation::Nearest |
Select nearest parsed frequency point; ties choose the lower point |
Extrapolation::Error |
Error outside the parsed frequency range |
Extrapolation::Clamp |
Hold nearest boundary S-parameters at the requested frequency |
For scalar reference impedance networks, stable matrix conversion APIs are available for common RF and circuit-simulation workflows:
| Item | Description |
|---|---|
SMatrix::to_y_matrix(z0) |
Convert S-parameters to admittance parameters |
SMatrix::to_z_matrix(z0) |
Convert S-parameters to impedance parameters |
SMatrix::to_abcd(z0) |
Convert a two-port S matrix to ABCD parameters |
SMatrix::try_from_y_matrix(matrix, z0) |
Convert Y parameters back to S-parameters |
SMatrix::try_from_z_matrix(matrix, z0) |
Convert Z parameters back to S-parameters |
SMatrix::try_from_abcd(matrix, z0) |
Convert ABCD parameters back to a two-port S matrix |
network.y_matrix_at(point_index) |
Y matrix for one parsed frequency point |
network.z_matrix_at(point_index) |
Z matrix for one parsed frequency point |
network.abcd_at(point_index) |
ABCD matrix for one two-port frequency point |
Each S-parameter data pair struct offers multiple accessors for the same underlying data:
| Struct | Field aliases |
|---|---|
RealImaginary |
.real(), .imaginary(), .magnitude(), .decibel(), .angle() |
DecibelAngle |
.decibel(), .angle(), .magnitude(), .real(), .imaginary() |
MagnitudeAngle |
.magnitude(), .angle(), .decibel(), .real(), .imaginary() |
You can also convert between representations:
| From → To | Method |
|---|---|
RealImaginary |
.magnitude_angle(), .decibel_angle() |
MagnitudeAngle |
.real_imaginary(), .decible_angle() (sic) |
DecibelAngle |
(convert via RealImaginary::from_decibel_angle) |
Save a Network back to disk. The writer auto-selects single-line format (1–2 ports) or
multi-line format (3+ ports):
use touchstone::Network;
let ntwk = Network::new("files/ntwk1.s2p")?;
ntwk.save("output.s2p").unwrap();For generated data, build a network from in-memory matrices and serialize without writing a file:
use touchstone::{Complex, NetworkBuilder, SMatrix};
let ntwk = NetworkBuilder::new("generated.s1p", 1)
.point(
1.0e9,
SMatrix {
rank: 1,
data: vec![vec![Complex { re: 0.5, im: -0.1 }]],
},
)
.build()?;
let touchstone = ntwk.to_touchstone_string()?;Combine two 2-port networks in series using direct S-parameter composition.
The standard cascade connects port 2 of the first network to port 1 of the second:
use touchstone::Network;
let net1 = Network::new("files/ntwk1.s2p")?;
let net2 = Network::new("files/ntwk2.s2p")?;
let cascaded = net1.cascade(&net2);
println!("Cascaded network has {} data points", cascaded.frequencies().len());For error handling, use net1.try_cascade(&net2)?. It returns a structured error
for incompatible inputs, singular internal connections, or nonfinite results.
cascade, multiplication, and cascade_ports retain their convenience signatures
and panic on those errors. Both networks must use the same common reference
impedance, frequency unit, and aligned frequency grid; unequal grids are rejected
instead of silently truncating. Resample explicitly when needed.
Direct S composition avoids the ABCD determinant cancellation that can corrupt reverse transmission at deep attenuation. ABCD conversion remains available as a separate API. Zero forward transmission is valid for a solvable connection. See cascade consistency for practical examples, accuracy checks, and numerical limitations.
For explicit port specification, use cascade_ports:
use touchstone::Network;
let net1 = Network::new("files/ntwk1.s2p")?;
let net2 = Network::new("files/ntwk2.s2p")?;
let cascaded = net1.cascade_ports(&net2, 2, 1);Plot a single Touchstone file:
touchstone files/ntwk3.s2pOutput:
============================
Single file detected. Plotting.
In file: files/ntwk3.s2p
'files/ntwk3.s2p' is a Relative path with separators (nested).
Plot HTML generated at files/ntwk3.s2p.html
You can open the plot in your browser at:
file:///Users/iancleary/Development/touchstone/files/ntwk3.s2p.html
Attempting to open plot in your default browser...
Success! Opening: file:///Users/iancleary/Development/touchstone/files/ntwk3.s2p.html
Works on Windows, macOS, and Linux file systems!
You can view the HTML source file itself here: examples/ntwk3.s2p.html.
Plot all Touchstone files in a directory:
touchstone files/Output:
============================
In directory: files/
Directory detected. Plotting all valid network files in directory.
Found network file: "files/ntwk1.s2p"
Found network file: "files/ntwk2.s2p"
Found network file: "files/ntwk3.s2p"
Plot HTML generated at files/combined_plot.html
You can view the HTML source file itself here: examples/combined_plot.html.
Cascade two or more 2-port networks from the command line:
# Cascade two networks
touchstone cascade ntwk1.s2p ntwk2.s2p
# Cascade with custom output name
touchstone cascade ntwk1.s2p ntwk2.s2p --name result.s2ptouchstone --helptouchstone uses tracing for structured, runtime-controllable diagnostics. Set the RUST_LOG environment variable to see what the CLI is doing:
# See file detection, plot generation, and cascade output paths
RUST_LOG=touchstone=info touchstone files/ntwk1.s2p
# See all diagnostics including per-file discovery in directories
RUST_LOG=touchstone=debug touchstone files/data_folder/
# Only warnings and errors (quiet mode)
RUST_LOG=touchstone=warn touchstone files/ntwk1.s2pIf you use touchstone as a library, install any tracing subscriber in your application to capture events. Without a subscriber, all tracing calls are zero-cost no-ops.
| Extension | Ports | Example use case |
|---|---|---|
.s1p |
1 | Terminations, loads, antennas |
.s2p |
2 | Amplifiers, filters, cables |
.s3p |
3 | Power dividers, circulators |
.s4p |
4 | Differential pairs, couplers |
.sNp |
N | Any N-port (tested up to 32-port) |
| Code | Name | Pair values |
|---|---|---|
RI |
Real-Imaginary | real, imaginary |
MA |
Magnitude-Angle | linear magnitude, degrees |
DB |
Decibel-Angle | dB magnitude, degrees |
Hz, kHz, MHz, and GHz are supported for parsing and writing with automatic
conversion. The builder/writer can emit THz, but the parser does not correctly
recognize it. See the support table before using this extension.
| Item | Description |
|---|---|
Network::new(path) |
Parse a Touchstone file and return errors |
Network::from_bytes(name, bytes) |
Parse in-memory UTF-8 Touchstone bytes |
Network::from_str(name, contents) |
Parse an in-memory Touchstone string |
NetworkBuilder::new(name, rank) |
Build generated S-parameter networks |
ReferenceImpedance::Common(z0) |
One scalar reference impedance |
ReferenceImpedance::PerPort(values) |
Per-port Touchstone v2 reference impedances |
Complex { re, im } |
Stable complex value used by public matrices |
SMatrix |
Stable full S-parameter matrix for one frequency |
SMatrix::try_new(data) |
Validate a nonempty, finite square S matrix |
ParameterMatrix |
Stable Y- or Z-parameter matrix |
ParameterMatrix::try_new(data) |
Validate a nonempty, finite square parameter matrix |
ABCDMatrix |
Stable two-port ABCD transmission matrix |
ABCDMatrix::try_new(a, b, c, d) |
Validate finite ABCD entries |
NetworkPoint::try_new(frequency, s) |
Validate a nonnegative finite frequency and S matrix |
Interpolation |
Linear or Nearest sampling policy |
Extrapolation |
Error or Clamp out-of-range policy |
network.rank |
Number of ports |
network.frequency_unit |
Frequency unit string |
network.format |
Data format (RI, MA, or DB) |
network.z0 |
Reference impedance (Ω) |
network.reference_impedance() |
Common or per-port reference metadata |
network.warnings |
Non-fatal parser diagnostics |
network.frequencies() |
Borrowed frequency grid (&[f64]) |
network.f() |
Clone of frequency vector |
network.point_ref(index) |
Borrow one frequency point and its S matrix |
network.iter_points() |
Iterate over borrowed frequency points |
network.s_db(j, k) |
S_jk in dB+angle — Vec<FrequencyDB> |
network.s_ri(j, k) |
S_jk in real+imag — Vec<FrequencyRI> |
network.s_ma(j, k) |
S_jk in mag+angle — Vec<FrequencyMA> |
network.s_matrix_at(point_index) |
Full S matrix for one frequency point |
network.sample_at(frequency_hz, interpolation, extrapolation) |
Sample at one frequency |
network.resample(frequencies_hz, interpolation, extrapolation) |
Return a new frequency grid |
network.y_matrix_at(point_index) |
Full Y matrix for one frequency point |
network.z_matrix_at(point_index) |
Full Z matrix for one frequency point |
network.abcd_at(point_index) |
Two-port ABCD matrix for one frequency point |
network.to_touchstone_string() |
Serialize Touchstone text in memory |
network.write_touchstone(writer) |
Write Touchstone text to any writer |
network.save(path) |
Write network to file |
network.try_cascade(&other) |
Cascade with structured error handling |
network.cascade(&other) |
Cascade two 2-port networks |
network.cascade_ports(&other, from, to) |
Cascade with explicit port mapping |
network.print_summary() |
Print metadata to stdout |

