rf-touchstone 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/AI_USAGE_GUIDE.md +701 -0
- package/coverage/coverage-badge.svg +4 -4
- package/dist/Touchstone.cjs.js +1 -1
- package/dist/Touchstone.es.js +109 -33
- package/dist/Touchstone.umd.js +1 -1
- package/dist/frequency.d.ts +33 -1
- package/dist/touchstone.d.ts +74 -21
- package/package.json +7 -2
- package/readme.md +27 -2
- package/src/frequency.ts +33 -1
- package/src/touchstone.ts +103 -24
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
# RF-Touchstone AI Usage Guide
|
|
2
|
+
|
|
3
|
+
> **For AI Coding Assistants**: This guide helps you understand how to integrate and use the `rf-touchstone` library in user projects.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Installation
|
|
8
|
+
|
|
9
|
+
**Via NPM/Yarn:**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install rf-touchstone
|
|
13
|
+
# or
|
|
14
|
+
yarn add rf-touchstone
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Via CDN:**
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<script src="https://unpkg.com/rf-touchstone"></script>
|
|
21
|
+
<script>
|
|
22
|
+
const { Touchstone, abs, arg, pi } = window.Touchstone
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Basic Import Pattern
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Touchstone } from 'rf-touchstone'
|
|
30
|
+
// Optional math helpers from mathjs
|
|
31
|
+
import { abs, arg, pi, complex } from 'rf-touchstone'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Touchstone Class
|
|
35
|
+
|
|
36
|
+
The `Touchstone` class is the primary interface for working with .snp files.
|
|
37
|
+
|
|
38
|
+
### Creating Touchstone Objects
|
|
39
|
+
|
|
40
|
+
**From URL (Browser or Node.js with fetch):**
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Auto-detect nports from filename (.s2p → 2-port)
|
|
44
|
+
const ts = await Touchstone.fromUrl('https://example.com/device.s2p')
|
|
45
|
+
console.log(ts.name) // 'device' - automatically extracted from filename
|
|
46
|
+
|
|
47
|
+
// Explicit nports if filename doesn't indicate
|
|
48
|
+
const ts = await Touchstone.fromUrl('https://example.com/data.txt', 2)
|
|
49
|
+
console.log(ts.name) // 'data' - basename without extension
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**From File Object (Browser file input):**
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// In any framework with file input
|
|
56
|
+
const handleFileUpload = async (file: File) => {
|
|
57
|
+
const touchstone = await Touchstone.fromFile(file)
|
|
58
|
+
console.log(touchstone.name) // Filename without extension
|
|
59
|
+
// Use touchstone object
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**From Text Content (Universal - works in any environment):**
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { Touchstone } from 'rf-touchstone'
|
|
67
|
+
|
|
68
|
+
// Example S2P file content (could come from any source: upload, API, database, etc.)
|
|
69
|
+
const s2pContent = `! 2-port S-parameter data
|
|
70
|
+
# GHz S MA R 50
|
|
71
|
+
1.0 0.50 -45 0.95 10 0.05 85 0.60 -30
|
|
72
|
+
2.0 0.45 -60 0.90 15 0.10 80 0.55 -45
|
|
73
|
+
3.0 0.40 -75 0.85 20 0.15 75 0.50 -60`
|
|
74
|
+
|
|
75
|
+
// Parse the text content with optional name
|
|
76
|
+
const touchstone = Touchstone.fromText(s2pContent, 2, 'my_measurement') // 2 = 2-port network
|
|
77
|
+
|
|
78
|
+
// Now you can use the parsed data
|
|
79
|
+
console.log(touchstone.name) // 'my_measurement'
|
|
80
|
+
console.log(touchstone.frequency.f_scaled) // [1.0, 2.0, 3.0]
|
|
81
|
+
console.log(touchstone.format) // 'MA'
|
|
82
|
+
console.log(touchstone.parameter) // 'S'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Accessing Touchstone Data
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
89
|
+
|
|
90
|
+
// Frequency data
|
|
91
|
+
const frequencies = ts.frequency.f_scaled // Array of numbers in specified unit
|
|
92
|
+
const unit = ts.frequency.unit // 'Hz' | 'kHz' | 'MHz' | 'GHz'
|
|
93
|
+
|
|
94
|
+
// Name property (automatically extracted from filename or manually set)
|
|
95
|
+
const name = ts.name // e.g., 'device' (without extension)
|
|
96
|
+
ts.name = 'modified_device' // Can be modified for plot legends, filenames, etc.
|
|
97
|
+
|
|
98
|
+
// Options line data
|
|
99
|
+
const format = ts.format // 'RI' | 'MA' | 'DB'
|
|
100
|
+
const parameter = ts.parameter // 'S' | 'Y' | 'Z' | 'G' | 'H'
|
|
101
|
+
const impedance = ts.impedance // typically 50
|
|
102
|
+
const nports = ts.nports // Number of ports (1, 2, 3, 4, etc.)
|
|
103
|
+
|
|
104
|
+
// Network parameter matrix
|
|
105
|
+
// IMPORTANT: matrix is 3D array [outPort][inPort][frequencyIndex]
|
|
106
|
+
const matrix = ts.matrix // Complex number matrix
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Understanding Matrix Indexing ⚠️ CRITICAL
|
|
110
|
+
|
|
111
|
+
**Matrix Structure:**
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// matrix[i][j][k]
|
|
115
|
+
// - i: Output port index (0 to nports-1) - where the signal exits
|
|
116
|
+
// - j: Input port index (0 to nports-1) - where the signal enters
|
|
117
|
+
// - k: Frequency point index (0 to numFrequencies-1)
|
|
118
|
+
//
|
|
119
|
+
// Sij = matrix[i-1][j-1][k] means: signal enters at port j, exits at port i
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Accessing S-parameters (consistent for ALL port counts):**
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// For 2-port network:
|
|
126
|
+
const s11 = ts.matrix[0][0][freqIdx] // S11
|
|
127
|
+
const s12 = ts.matrix[0][1][freqIdx] // S12
|
|
128
|
+
const s21 = ts.matrix[1][0][freqIdx] // S21
|
|
129
|
+
const s22 = ts.matrix[1][1][freqIdx] // S22
|
|
130
|
+
|
|
131
|
+
// For 4-port network (same pattern):
|
|
132
|
+
const s11 = ts.matrix[0][0][freqIdx] // S11
|
|
133
|
+
const s21 = ts.matrix[1][0][freqIdx] // S21
|
|
134
|
+
const s31 = ts.matrix[2][0][freqIdx] // S31
|
|
135
|
+
const s41 = ts.matrix[3][0][freqIdx] // S41
|
|
136
|
+
const s12 = ts.matrix[0][1][freqIdx] // S12
|
|
137
|
+
// ... and so on
|
|
138
|
+
|
|
139
|
+
// General pattern: Sij = ts.matrix[i-1][j-1][freqIdx]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Format Conversion
|
|
143
|
+
|
|
144
|
+
To convert between formats (RI, MA, DB):
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
148
|
+
|
|
149
|
+
// Method 1: Change format and regenerate content
|
|
150
|
+
ts.format = 'DB' // Change to Decibel/Angle
|
|
151
|
+
const dbContent = ts.writeContent() // Generate new file content
|
|
152
|
+
// Now you can save dbContent or parse it as a new Touchstone object
|
|
153
|
+
|
|
154
|
+
// Method 2: Create new instance with different format
|
|
155
|
+
const dbTs = Touchstone.fromText(dbContent, ts.nports)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Writing Touchstone Content
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
162
|
+
|
|
163
|
+
// Generate Touchstone file content in current format
|
|
164
|
+
const content = ts.writeContent()
|
|
165
|
+
|
|
166
|
+
// In Browser - Download as file
|
|
167
|
+
const blob = new Blob([content], { type: 'text/plain' })
|
|
168
|
+
const url = URL.createObjectURL(blob)
|
|
169
|
+
const a = document.createElement('a')
|
|
170
|
+
a.href = url
|
|
171
|
+
a.download = 'modified-device.s2p'
|
|
172
|
+
a.click()
|
|
173
|
+
|
|
174
|
+
// In Node.js - Save to file
|
|
175
|
+
import fs from 'fs/promises'
|
|
176
|
+
await fs.writeFile('modified-device.s2p', content)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Working with Complex Numbers
|
|
180
|
+
|
|
181
|
+
The library uses mathjs Complex type `{ re: number, im: number }`:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { abs, arg, pi } from 'rf-touchstone'
|
|
185
|
+
|
|
186
|
+
// Get S11 at first frequency for 2-port
|
|
187
|
+
const s11 = ts.matrix[0][0][0] // Complex { re: ..., im: ... }
|
|
188
|
+
|
|
189
|
+
// Calculate magnitude
|
|
190
|
+
const magnitude = abs(s11) // |S11|
|
|
191
|
+
|
|
192
|
+
// Calculate phase (in radians)
|
|
193
|
+
const phase = arg(s11) // ∠S11
|
|
194
|
+
|
|
195
|
+
// Convert to dB
|
|
196
|
+
const magnitudeDB = 20 * Math.log10(magnitude)
|
|
197
|
+
|
|
198
|
+
// Convert phase to degrees
|
|
199
|
+
const phaseDegrees = (phase * 180) / pi
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Frequency Class
|
|
203
|
+
|
|
204
|
+
The `Frequency` class provides powerful utilities for unit conversion and wavelength calculations. While it's typically accessed through `touchstone.frequency`, you can also work with it directly.
|
|
205
|
+
|
|
206
|
+
### Accessing Frequency Data
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
210
|
+
|
|
211
|
+
// Access frequency points in the file's original unit
|
|
212
|
+
const frequencies = ts.frequency.f_scaled // e.g., [1.0, 2.0, 3.0]
|
|
213
|
+
const unit = ts.frequency.unit // e.g., 'GHz'
|
|
214
|
+
|
|
215
|
+
// Get frequency points in different units
|
|
216
|
+
const freqsInHz = ts.frequency.f_Hz // [1e9, 2e9, 3e9]
|
|
217
|
+
const freqsInMHz = ts.frequency.f_MHz // [1000, 2000, 3000]
|
|
218
|
+
const freqsInGHz = ts.frequency.f_GHz // [1.0, 2.0, 3.0]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Converting Frequency Units
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
225
|
+
|
|
226
|
+
// Get current unit
|
|
227
|
+
console.log(ts.frequency.unit) // 'GHz'
|
|
228
|
+
console.log(ts.frequency.f_scaled) // [1.0, 2.0, 3.0]
|
|
229
|
+
|
|
230
|
+
// Change unit - automatically rescales f_scaled
|
|
231
|
+
ts.frequency.unit = 'MHz'
|
|
232
|
+
console.log(ts.frequency.f_scaled) // [1000, 2000, 3000]
|
|
233
|
+
|
|
234
|
+
// Access in any unit without changing internal representation
|
|
235
|
+
const inHz = ts.frequency.f_Hz // [1e9, 2e9, 3e9]
|
|
236
|
+
const inTHz = ts.frequency.f_THz // [0.001, 0.002, 0.003]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> [!IMPORTANT]
|
|
240
|
+
> **THz Support**: While THz can be accessed programmatically via `f_THz` getter/setter, it is **not** an official unit in Touchstone v1.x/v2.x file formats. Do not set `frequency.unit = 'THz'` as it will throw an error. THz is only available through the `f_THz` property for conversion purposes.
|
|
241
|
+
|
|
242
|
+
### Working with Wavelength
|
|
243
|
+
|
|
244
|
+
The `Frequency` class can convert between frequency and wavelength using the relationship $\lambda = c/f$ where $c$ is the speed of light (299,792,458 m/s).
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
248
|
+
|
|
249
|
+
// Get wavelengths in different units
|
|
250
|
+
const wavelengthsInM = ts.frequency.wavelength_m // meters
|
|
251
|
+
const wavelengthsInCm = ts.frequency.wavelength_cm // centimeters
|
|
252
|
+
const wavelengthsInMm = ts.frequency.wavelength_mm // millimeters
|
|
253
|
+
const wavelengthsInUm = ts.frequency.wavelength_um // micrometers
|
|
254
|
+
const wavelengthsInNm = ts.frequency.wavelength_nm // nanometers
|
|
255
|
+
|
|
256
|
+
// Example: 1 GHz → λ ≈ 0.3 m = 30 cm = 300 mm
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Setting Frequency from Wavelength
|
|
260
|
+
|
|
261
|
+
You can also set frequency points by specifying wavelengths. This is bidirectional - setting wavelength updates the underlying frequency data:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { Frequency } from 'rf-touchstone'
|
|
265
|
+
|
|
266
|
+
const freq = new Frequency()
|
|
267
|
+
freq.unit = 'GHz'
|
|
268
|
+
|
|
269
|
+
// Set frequencies using wavelength in millimeters
|
|
270
|
+
// For λ = 300 mm → f ≈ 1 GHz
|
|
271
|
+
// For λ = 150 mm → f ≈ 2 GHz
|
|
272
|
+
freq.wavelength_mm = [300, 150, 100]
|
|
273
|
+
|
|
274
|
+
console.log(freq.f_GHz) // [~1.0, ~2.0, ~3.0]
|
|
275
|
+
console.log(freq.f_scaled) // [~1.0, ~2.0, ~3.0]
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Practical Example: Frequency Range Analysis
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
async function analyzeFrequencyRange(file: File) {
|
|
282
|
+
const ts = await Touchstone.fromFile(file)
|
|
283
|
+
|
|
284
|
+
const freqs = ts.frequency.f_GHz
|
|
285
|
+
const wavelengths = ts.frequency.wavelength_mm
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
numPoints: freqs.length,
|
|
289
|
+
range: {
|
|
290
|
+
frequency: {
|
|
291
|
+
min: freqs[0],
|
|
292
|
+
max: freqs[freqs.length - 1],
|
|
293
|
+
unit: 'GHz',
|
|
294
|
+
},
|
|
295
|
+
wavelength: {
|
|
296
|
+
min: wavelengths[wavelengths.length - 1], // shortest
|
|
297
|
+
max: wavelengths[0], // longest
|
|
298
|
+
unit: 'mm',
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
// Calculate frequency spacing
|
|
302
|
+
spacing:
|
|
303
|
+
freqs.length > 1
|
|
304
|
+
? ((freqs[freqs.length - 1] - freqs[0]) / (freqs.length - 1)).toFixed(3)
|
|
305
|
+
: 'N/A',
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Common Use Cases
|
|
311
|
+
|
|
312
|
+
### Example 1: File Upload in Browser (Vanilla JavaScript)
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { Touchstone, abs, arg, pi } from 'rf-touchstone'
|
|
316
|
+
|
|
317
|
+
// HTML: <input type="file" id="fileInput" accept=".s*p">
|
|
318
|
+
const fileInput = document.getElementById('fileInput') as HTMLInputElement
|
|
319
|
+
|
|
320
|
+
fileInput.addEventListener('change', async (event) => {
|
|
321
|
+
const file = (event.target as HTMLInputElement).files?![0]
|
|
322
|
+
if (!file) return
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const ts = await Touchstone.fromFile(file)
|
|
326
|
+
|
|
327
|
+
console.log(`${ts.nports}-Port Network`)
|
|
328
|
+
console.log(`Frequencies: ${ts.frequency.f_scaled.length}`)
|
|
329
|
+
console.log(`Unit: ${ts.frequency.unit}`)
|
|
330
|
+
console.log(`Format: ${ts.format}`)
|
|
331
|
+
|
|
332
|
+
// Access S11 at first frequency (works for any port count)
|
|
333
|
+
const s11 = ts.matrix[0][0][0]
|
|
334
|
+
console.log(`S11 Magnitude: ${abs(s11)}`)
|
|
335
|
+
console.log(`S11 Phase (deg): ${(arg(s11) * 180 / pi).toFixed(2)}`)
|
|
336
|
+
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error('Error loading Touchstone file:', error)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Example 2: Format Converter (Node.js)
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { Touchstone } from 'rf-touchstone'
|
|
347
|
+
import fs from 'fs/promises'
|
|
348
|
+
|
|
349
|
+
async function convertTouchstone(
|
|
350
|
+
inputPath: string,
|
|
351
|
+
outputPath: string,
|
|
352
|
+
targetFormat: 'RI' | 'MA' | 'DB'
|
|
353
|
+
) {
|
|
354
|
+
// Read input file
|
|
355
|
+
const content = await fs.readFile(inputPath, 'utf-8')
|
|
356
|
+
|
|
357
|
+
// Determine nports from filename
|
|
358
|
+
const nports = Touchstone.parsePorts(inputPath)
|
|
359
|
+
if (nports === null) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Cannot determine number of ports from filename: ${inputPath}`
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Parse the file
|
|
366
|
+
const ts = Touchstone.fromText(content, nports)
|
|
367
|
+
|
|
368
|
+
console.log(`Original format: ${ts.format}`)
|
|
369
|
+
|
|
370
|
+
// Convert format
|
|
371
|
+
ts.format = targetFormat
|
|
372
|
+
const convertedContent = ts.writeContent()
|
|
373
|
+
|
|
374
|
+
// Write output
|
|
375
|
+
await fs.writeFile(outputPath, convertedContent)
|
|
376
|
+
|
|
377
|
+
console.log(`Converted to ${targetFormat}`)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Usage
|
|
381
|
+
await convertTouchstone('input.s2p', 'output.s2p', 'DB')
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Example 3: S-Parameter Plotting (Any Framework)
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { Touchstone, abs } from 'rf-touchstone'
|
|
388
|
+
|
|
389
|
+
async function getPlotData(url: string) {
|
|
390
|
+
const ts = await Touchstone.fromUrl(url)
|
|
391
|
+
|
|
392
|
+
const numFreqs = ts.frequency.f_scaled.length
|
|
393
|
+
const frequencies = ts.frequency.f_scaled
|
|
394
|
+
|
|
395
|
+
// Extract S21 magnitude in dB for all frequencies (2-port network)
|
|
396
|
+
const s21_dB: number[] = []
|
|
397
|
+
for (let freqIdx = 0; freqIdx < numFreqs; freqIdx++) {
|
|
398
|
+
const s21 = ts.matrix[1][0][freqIdx] // S21 for 2-port
|
|
399
|
+
const magnitude = abs(s21)
|
|
400
|
+
s21_dB.push(20 * Math.log10(magnitude))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
frequencies,
|
|
405
|
+
s21_dB,
|
|
406
|
+
unit: ts.frequency.unit,
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Use with any chart library (Chart.js, D3, Plotly, etc.)
|
|
411
|
+
const data = await getPlotData('https://example.com/device.s2p')
|
|
412
|
+
console.log(data)
|
|
413
|
+
// { frequencies: [...], s21_dB: [...], unit: 'GHZ' }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Example 4: Data Analysis
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { Touchstone, abs } from 'rf-touchstone'
|
|
420
|
+
|
|
421
|
+
async function analyzeTouchstone(file: File) {
|
|
422
|
+
const ts = await Touchstone.fromFile(file)
|
|
423
|
+
|
|
424
|
+
if (ts.nports !== 2) {
|
|
425
|
+
throw new Error('This example is for 2-port networks only')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const numFreqs = ts.frequency.f_scaled.length
|
|
429
|
+
|
|
430
|
+
// Calculate insertion loss (S21)
|
|
431
|
+
let maxS21 = 0
|
|
432
|
+
for (let i = 0; i < numFreqs; i++) {
|
|
433
|
+
const mag = abs(ts.matrix[1][0][i]) // S21
|
|
434
|
+
if (mag > maxS21) maxS21 = mag
|
|
435
|
+
}
|
|
436
|
+
const insertionLoss_dB = -20 * Math.log10(maxS21)
|
|
437
|
+
|
|
438
|
+
// Calculate return loss (S11)
|
|
439
|
+
let maxS11 = 0
|
|
440
|
+
for (let i = 0; i < numFreqs; i++) {
|
|
441
|
+
const mag = abs(ts.matrix[0][0][i]) // S11
|
|
442
|
+
if (mag > maxS11) maxS11 = mag
|
|
443
|
+
}
|
|
444
|
+
const returnLoss_dB = -20 * Math.log10(maxS11)
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
nports: ts.nports,
|
|
448
|
+
numFrequencies: numFreqs,
|
|
449
|
+
frequencyRange: {
|
|
450
|
+
min: ts.frequency.f_scaled[0],
|
|
451
|
+
max: ts.frequency.f_scaled[numFreqs - 1],
|
|
452
|
+
unit: ts.frequency.unit,
|
|
453
|
+
},
|
|
454
|
+
insertionLoss_dB: insertionLoss_dB.toFixed(2),
|
|
455
|
+
returnLoss_dB: returnLoss_dB.toFixed(2),
|
|
456
|
+
format: ts.format,
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Example 5: Vue Component
|
|
462
|
+
|
|
463
|
+
```vue
|
|
464
|
+
<template>
|
|
465
|
+
<div>
|
|
466
|
+
<input type="file" @change="handleFileUpload" accept=".s*p" />
|
|
467
|
+
<div v-if="touchstone">
|
|
468
|
+
<h2>{{ touchstone.nports }}-Port Network</h2>
|
|
469
|
+
<p>Frequencies: {{ touchstone.frequency.f_scaled.length }}</p>
|
|
470
|
+
<p>Unit: {{ touchstone.frequency.unit }}</p>
|
|
471
|
+
<p>Format: {{ touchstone.format }}</p>
|
|
472
|
+
<button @click="convertToDb">Convert to dB</button>
|
|
473
|
+
<button @click="downloadFile">Download</button>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</template>
|
|
477
|
+
|
|
478
|
+
<script setup lang="ts">
|
|
479
|
+
import { ref } from 'vue'
|
|
480
|
+
import { Touchstone } from 'rf-touchstone'
|
|
481
|
+
|
|
482
|
+
const touchstone = ref<Touchstone | null>(null)
|
|
483
|
+
|
|
484
|
+
async function handleFileUpload(event: Event) {
|
|
485
|
+
const file = (event.target as HTMLInputElement).files?.[0]
|
|
486
|
+
if (file) {
|
|
487
|
+
touchstone.value = await Touchstone.fromFile(file)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function convertToDb() {
|
|
492
|
+
if (touchstone.value) {
|
|
493
|
+
touchstone.value.format = 'DB'
|
|
494
|
+
// Re-parse to get converted matrix
|
|
495
|
+
const content = touchstone.value.writeContent()
|
|
496
|
+
touchstone.value = Touchstone.fromText(content, touchstone.value.nports!)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function downloadFile() {
|
|
501
|
+
if (touchstone.value) {
|
|
502
|
+
const content = touchstone.value.writeContent()
|
|
503
|
+
const blob = new Blob([content], { type: 'text/plain' })
|
|
504
|
+
const url = URL.createObjectURL(blob)
|
|
505
|
+
const a = document.createElement('a')
|
|
506
|
+
a.href = url
|
|
507
|
+
a.download = `converted.s${touchstone.value.nports}p`
|
|
508
|
+
a.click()
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
</script>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## TypeScript Types
|
|
515
|
+
|
|
516
|
+
### Key Interfaces and Types
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// Touchstone class
|
|
520
|
+
class Touchstone {
|
|
521
|
+
// Properties
|
|
522
|
+
name?: string // Filename without extension, used for legends and default filenames
|
|
523
|
+
nports?: number
|
|
524
|
+
frequency?: Frequency
|
|
525
|
+
format?: 'RI' | 'MA' | 'DB'
|
|
526
|
+
parameter?: 'S' | 'Y' | 'Z' | 'G' | 'H'
|
|
527
|
+
impedance: number | number[] // default: 50
|
|
528
|
+
matrix?: Complex[][][] // [outPort][inPort][freqIdx]
|
|
529
|
+
comments: string[]
|
|
530
|
+
|
|
531
|
+
// Static methods
|
|
532
|
+
static fromText(content: string, nports: number, name?: string): Touchstone
|
|
533
|
+
static fromUrl(url: string, nports?: number): Promise<Touchstone>
|
|
534
|
+
static fromFile(file: File, nports?: number): Promise<Touchstone>
|
|
535
|
+
static getFilename(pathOrUrl: string): string
|
|
536
|
+
static getBasename(filenameOrPath: string): string
|
|
537
|
+
static parsePorts(filename: string): number | null
|
|
538
|
+
|
|
539
|
+
// Instance methods
|
|
540
|
+
readContent(content: string, nports: number): void
|
|
541
|
+
writeContent(): string
|
|
542
|
+
validate(): void
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Complex number (from mathjs)
|
|
546
|
+
interface Complex {
|
|
547
|
+
re: number // Real part
|
|
548
|
+
im: number // Imaginary part
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Frequency class
|
|
552
|
+
class Frequency {
|
|
553
|
+
unit: 'Hz' | 'kHz' | 'MHz' | 'GHz'
|
|
554
|
+
f_scaled: number[] // Frequency values in current unit
|
|
555
|
+
|
|
556
|
+
// Getters/setters for different units
|
|
557
|
+
f_Hz: number[]
|
|
558
|
+
f_kHz: number[]
|
|
559
|
+
f_MHz: number[]
|
|
560
|
+
f_GHz: number[]
|
|
561
|
+
f_THz: number[]
|
|
562
|
+
|
|
563
|
+
// Wavelength getters/setters
|
|
564
|
+
wavelength_m: number[]
|
|
565
|
+
wavelength_cm: number[]
|
|
566
|
+
wavelength_mm: number[]
|
|
567
|
+
wavelength_um: number[]
|
|
568
|
+
wavelength_nm: number[]
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Helper Functions (from mathjs)
|
|
572
|
+
// These can be imported: import { abs, arg, pi } from 'rf-touchstone'
|
|
573
|
+
function abs(x: Complex | number): number
|
|
574
|
+
function arg(x: Complex): number // Returns phase in radians
|
|
575
|
+
function complex(re: number, im: number): Complex
|
|
576
|
+
function complex(obj: { r: number; phi: number }): Complex // Polar form
|
|
577
|
+
const pi: number
|
|
578
|
+
|
|
579
|
+
// Other available helpers:
|
|
580
|
+
// add, multiply, pow, log10, round, range, index, subset
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Important Notes
|
|
584
|
+
|
|
585
|
+
### Module Formats Support
|
|
586
|
+
|
|
587
|
+
This library supports **multiple module formats** for maximum compatibility:
|
|
588
|
+
|
|
589
|
+
- **UMD (Universal Module Definition)**: `dist/Touchstone.umd.js` - For direct browser `<script>` tags
|
|
590
|
+
- **ESM (ES Modules)**: `dist/Touchstone.es.js` - For modern bundlers and `import` statements
|
|
591
|
+
- **CJS (CommonJS)**: `dist/Touchstone.cjs.js` - For older Node.js projects using `require()`
|
|
592
|
+
|
|
593
|
+
**Using in Browser (UMD):**
|
|
594
|
+
|
|
595
|
+
Via CDN:
|
|
596
|
+
|
|
597
|
+
```html
|
|
598
|
+
<!-- Using unpkg -->
|
|
599
|
+
<script src="https://unpkg.com/rf-touchstone"></script>
|
|
600
|
+
|
|
601
|
+
<!-- Or using jsDelivr -->
|
|
602
|
+
<script src="https://cdn.jsdelivr.net/npm/rf-touchstone"></script>
|
|
603
|
+
|
|
604
|
+
<script>
|
|
605
|
+
const { Touchstone, abs, arg } = window.Touchstone
|
|
606
|
+
</script>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Using in Modern Projects (ESM):**
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
import { Touchstone } from 'rf-touchstone'
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Using in Legacy Node.js (CommonJS):**
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
const { Touchstone } = require('rf-touchstone')
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Browser vs Node.js
|
|
622
|
+
|
|
623
|
+
- `Touchstone.fromUrl()` works in both environments (uses `fetch`)
|
|
624
|
+
- `Touchstone.fromFile()` is browser-only (uses `File` API)
|
|
625
|
+
- For Node.js file reading, use `fs` and `Touchstone.fromText()`
|
|
626
|
+
|
|
627
|
+
### Port Indexing
|
|
628
|
+
|
|
629
|
+
**Matrix indexing is consistent for ALL port counts:**
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
// General pattern: Sij = matrix[i-1][j-1][freqIdx]
|
|
633
|
+
|
|
634
|
+
// For any N-port network:
|
|
635
|
+
const s11 = matrix[0][0][freqIdx] // S11
|
|
636
|
+
const s21 = matrix[1][0][freqIdx] // S21
|
|
637
|
+
const s12 = matrix[0][1][freqIdx] // S12
|
|
638
|
+
const s22 = matrix[1][1][freqIdx] // S22
|
|
639
|
+
// etc.
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Memory Considerations
|
|
643
|
+
|
|
644
|
+
Large .snp files (many ports/frequencies) create large matrices:
|
|
645
|
+
|
|
646
|
+
- Processing in chunks
|
|
647
|
+
- Not storing multiple copies simultaneously
|
|
648
|
+
|
|
649
|
+
## Error Handling
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
try {
|
|
653
|
+
const ts = await Touchstone.fromUrl('device.s2p')
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (error instanceof Error) {
|
|
656
|
+
// Common errors:
|
|
657
|
+
// - Network errors (URL fetch failed)
|
|
658
|
+
// - Parse errors (invalid Touchstone format)
|
|
659
|
+
// - File not found
|
|
660
|
+
// - Could not determine nports
|
|
661
|
+
console.error('Failed to load Touchstone:', error.message)
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## Resources
|
|
667
|
+
|
|
668
|
+
- **API Documentation**: https://panz2018.github.io/RF-Touchstone/api/modules
|
|
669
|
+
- **GitHub**: https://github.com/panz2018/RF-Touchstone
|
|
670
|
+
- **NPM**: https://www.npmjs.com/package/rf-touchstone
|
|
671
|
+
|
|
672
|
+
## Common Patterns Summary
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
// ✅ DO: Load from URL
|
|
676
|
+
const ts = await Touchstone.fromUrl('file.s2p')
|
|
677
|
+
|
|
678
|
+
// ✅ DO: Access matrix correctly (same for all port counts)
|
|
679
|
+
const s21 = ts.matrix[1][0][freqIdx] // S21
|
|
680
|
+
|
|
681
|
+
// ✅ DO: Convert format properly
|
|
682
|
+
ts.format = 'DB'
|
|
683
|
+
const dbContent = ts.writeContent()
|
|
684
|
+
|
|
685
|
+
// ✅ DO: Iterate frequencies correctly
|
|
686
|
+
for (let freqIdx = 0; freqIdx < ts.frequency.f_scaled.length; freqIdx++) {
|
|
687
|
+
const s11 = ts.matrix[0][0][freqIdx]
|
|
688
|
+
// process s11...
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ❌ DON'T: Use non-existent convertFormat method
|
|
692
|
+
// const tsDB = ts.convertFormat('DB') // This doesn't exist!
|
|
693
|
+
|
|
694
|
+
// ❌ DON'T: Use wrong matrix indexing
|
|
695
|
+
// const s11 = ts.matrix[freqIdx][0][0] // WRONG!
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
**Last Updated**: 2026-01-25
|
|
701
|
+
**Library Version**: 0.0.5+
|