# touchstone
Touchstone (SNP) parser for RF Engineering — Full N-Port Support
Parse, analyze, and manipulate Touchstone files with any number of ports (1-port, 2-port, 3-port, 4-port, and beyond).
## Installation
```bash
cargo install touchstone
```
This installs an executable in your `.cargo/bin` directory (`.cargo/bin/touchstone`).
---
## 1. What Are Touchstone Files?
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:
```text
! 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 Ω).
---
## 2. Loading a Network
Use `Network::new` to parse any Touchstone file:
```rust
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.f.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:
```rust
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`:
```rust
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)`.
---
## 3. Accessing S-Parameters
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:
| `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 }` |
```rust
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.
### Interpolation and Resampling
`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.
| `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 |
### Network Parameter Conversions
For scalar reference impedance networks, stable matrix conversion APIs are available for common RF
and circuit-simulation workflows:
| `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 |
### Field Aliases
Each S-parameter data pair struct offers multiple accessors for the same underlying data:
| `RealImaginary` | `.real()`, `.imaginary()`, `.magnitude()`, `.decibel()`, `.angle()` |
| `DecibelAngle` | `.decibel()`, `.angle()`, `.magnitude()`, `.real()`, `.imaginary()` |
| `MagnitudeAngle` | `.magnitude()`, `.angle()`, `.decibel()`, `.real()`, `.imaginary()` |
You can also convert between representations:
| `RealImaginary` | `.magnitude_angle()`, `.decibel_angle()` |
| `MagnitudeAngle` | `.real_imaginary()`, `.decible_angle()` (sic) |
| `DecibelAngle` | (convert via `RealImaginary::from_decibel_angle`) |
---
## 4. Saving Networks
Save a `Network` back to disk. The writer auto-selects single-line format (1–2 ports) or
multi-line format (3+ ports):
```rust
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:
```rust
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()?;
```
---
## 5. Cascading 2-Port Networks
Combine two 2-port networks in series using the ABCD parameter method.
The standard `cascade` connects port 2 of the first network to port 1 of the second:
```rust
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.f.len());
```
For explicit port specification, use `cascade_ports`:
```rust
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);
```
---
## 6. CLI Usage
### File Path
Plot a single Touchstone file:
```bash
touchstone files/ntwk3.s2p
```
Output:
```text
============================
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!
[](https://github.com/iancleary/touchstone/tree/main/examples/ntwk3.s2p.html)
You can view the HTML source file itself here: [examples/ntwk3.s2p.html](https://github.com/iancleary/touchstone/tree/main/examples/ntwk3.s2p.html).
### Directory Path
Plot all Touchstone files in a directory:
```bash
touchstone files/
```
Output:
```text
============================
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
```
[](https://github.com/iancleary/touchstone/tree/main/examples/combined_plot.html)
You can view the HTML source file itself here: [examples/combined_plot.html](https://github.com/iancleary/touchstone/tree/main/examples/combined_plot.html).
### Cascade Command
Cascade two or more 2-port networks from the command line:
```bash
# Cascade two networks
touchstone cascade ntwk1.s2p ntwk2.s2p
# Cascade with custom output name
touchstone cascade ntwk1.s2p ntwk2.s2p --name result.s2p
```
### Full Help
```bash
touchstone --help
```
### Diagnostics (Tracing)
`touchstone` uses [`tracing`](https://docs.rs/tracing) for structured, runtime-controllable diagnostics. Set the `RUST_LOG` environment variable to see what the CLI is doing:
```bash
# 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.s2p
```
If 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.
---
## 7. Supported File Types, Data Formats, and Frequency Units
### File Types
| `.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) |
### Data Formats
| `RI` | Real-Imaginary | real, imaginary |
| `MA` | Magnitude-Angle | linear magnitude, degrees|
| `DB` | Decibel-Angle | dB magnitude, degrees |
### Frequency Units
`Hz`, `kHz`, `MHz`, `GHz`, `THz` — all supported with automatic conversion.
---
## 8. API Summary
| `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 |
| `ParameterMatrix` | Stable Y- or Z-parameter matrix |
| `ABCDMatrix` | Stable two-port ABCD transmission 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.f` | Frequency vector (`Vec<f64>`) |
| `network.f()` | Clone of frequency vector |
| `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.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 |
---
## References
* [Touchstone Wikipedia entry](https://en.wikipedia.org/wiki/Touchstone_file)
* [Touchstone File Format Specification Version 2.1](https://ibis.org/touchstone_ver2.1/touchstone_ver2_1.pdf)
* Local Version of [Touchstone File Format Specification Version 2.1](https://github.com/iancleary/touchstone/blob/main//docs/touchstone_ver2_1.pdf)