touchstone

    Portable Touchstone network IO and RF parameter analysis

    Download zip
    Version
    0.2.0
    License
    MIT
    Last updated
    5 hours ago
    Downloads
    1

    #MoonTouchstone

    当前本地版本 0.2.0,作为旧象棋题目的替换候选。申报正文和厂家公开 S2P 实证说明用途、上游关系与尚未完成的公开交付。新增纯 MoonBit 通带峰峰插损判定,不以合成样例冒充用户采用。

    评审/首次使用请先看实际任务、替代方案与可运行证据:导入Touchstone,按适用条件进行阻抗变换、级联或去嵌入,再对指定频段的采样点计算插损、回损等指标,输出超限位置或无法判定的原因。

    MoonBit 原生 Touchstone 文件库与小规模 RF 网络数据工具。格式解析、SI 归一化、复数矩阵、参数转换和 RF 分析全部由 MoonBit 实现;Node 仅承担 CLI 文件读写和参数传递。MIT 许可,AI 辅助开发,保留真实 Git 作者与开发过程。

    guolei-dev/touchstone 是本地开发命名空间,尚未发布。本地完成不等于远程 CI 或正式比赛验收通过。固定开发范围见 SCOPE,验证记录见 TESTING。

    #已有能力

    • Touchstone 1.0/1.1/2.0/2.1:RI/MA/DB,Hz/kHz/MHz/GHz,逐端口正实参考阻抗,二端口两种顺序,多行数据,全矩阵/上下三角。
    • 旧版 Y/Z/H/G 归一化转为物理 SI;2.x 非 S 参数本来就是物理量,不能再乘参考电阻。
    • 噪声参数、独立噪声参考电阻、混合模端口描述和信息段保存。规范化写出 2.1;普通网络可写 1.0/1.1。
    • S/Y/Z/H/G 转换(H/G 限二端口),重归一化,端口选择,复平面线性插值,二端口级联和双夹具去嵌入。
    • 互易误差、完整矩阵无源性诊断、群时延、回波损耗、插入损耗和 VSWR。
    • 多频段采样点限值检查:逐规则判定、原始频点定位、极值、全量计数、限额明细和 CSV;没有采样或覆盖不足不会假通过。
    • 输入严格检查;不隐式丢弃噪声/混合模元数据;现有输出文件不会被 CLI 覆盖。

    #本地运行

    需要 MoonBit 工具链和 Node.js 24。2026-09-28 在 Windows 使用 .moonbit-version 固定的 moonc 0.10.14+7d59c7ec9 / moon 0.1.20260920,JS 与 Wasm-GC 检查、测试和示例均已通过。CI 固定同一版本并初始化 registry,目前仅配置 Ubuntu,尚未核实远程状态。库本身不依赖 Python 或 scikit-rf。

    moon check --target all moon test --target js moon test --target wasm-gc moon build --target js --release node tools/touchstone.mjs inspect examples/attenuator.s2p node tools/touchstone.mjs metrics examples/attenuator.s2p '{"input":0,"output":1}' node tools/touchstone.mjs delay examples/attenuator.s2p '{"input":0,"output":1}' node tools/touchstone.mjs band-check examples/attenuator.s2p @examples/band-limits.json moon run examples/band_check --target js node tools/touchstone.mjs normalize examples/attenuator.s2p '{}' normalized.ts node tools/touchstone.mjs convert examples/attenuator.s2p '{"parameter":"Z"}' impedance.ts node tools/touchstone.mjs renormalize examples/attenuator.s2p '{"reference_ohms":[75,75]}' at75.ts node tools/touchstone.mjs cascade examples/attenuator.s2p '{"right_file":"examples/attenuator.s2p"}' cascaded.ts node tools/touchstone.mjs deembed cascaded.ts '{"left_file":"examples/attenuator.s2p","right_file":"examples/attenuator.s2p"}' recovered.ts

    以上 JSON 单引号适用 PowerShell 7 / POSIX shell,Windows cmd 需要按该 shell 的规则转义引号。示例是匹配 6.0206 dB 衰减器及 1 ns 延迟,不依赖下载文件。两只级联再去掉两只夹具得到理想直通,不能要求其有限 Z 表示。

    CLI 形式为 COMMAND INPUT [OPTIONS-JSON] [OUTPUT],选项也可用 @path/to/options.json(ASCII、≤1 MB)。不指定输出则打印结果;报告为 JSON,转换为 Touchstone 文本。输入/IO 错误退出 2;band-check / band-csv 返回 0(采样点通过)、3(超限)、4(无法判定),会先写出报告再返回状态码。旧文件 .sNp 可推断端口数,其余旧格式文件需 {"ports":N};2.x 若同时给出端口数必须与头一致。端口、选择下标、预算必须是精确整数,小数不会自动截断。全部命令见 --help。

    命令选项/行为
    inspect / validate / dump头、元数据 / 完整校验 / 原始逐点矩阵
    normalize / legacy保留元数据写 2.1 / 普通网络写旧格式,可选 format、unit
    convert / renormalizeparameter / reference_ohms
    select / interpolateselection 零起点端口数组 / frequency_hz 递增目标频率
    cascade / deembedright_file / left_file 和 right_file
    diagnostics可选 tolerance,默认 1e-9
    metrics / delayinput、output 零起点端口
    band-check / band-csvlimits 规则数组;max_details 全局明细额度;完整指南
    ripple-checkstart_hz、end_hz、input、output、maximum_ripple_db;公开数据与聚合语义

    examples/band_check 是不依赖 Node 文件宿主的纯 MoonBit 示例,可用 JS / Wasm-GC 运行。示例故意设置一条不通过的回波损耗要求,展示 JSON 与原始超限点 CSV;它正常执行并输出 fail 不代表测试失败。

    #公共 API 与数值约定

    完整签名在 pkg.generated.mbti。根包为纯 MoonBit,导入模块后可调用:

    let doc = @touchstone.parse(text, ports=2)
    let net = doc.network_only()
    let s75 = net.renormalize([75.0, 75.0])
    let output = @touchstone.document(s75).write()

    这些函数可抛错,调用代码需处理错误。network(ports, frequency_hz, values, reference_ohms, parameter="S") 也可直接构造网络。每个矩阵按行存放,row * ports + column;S[out,in] 指定传输方向。频率 Hz,参考电阻 ohm,Z 为 ohm,Y 为 siemens;H/G 各分量单位不同。构造与查询都复制数组,不暴露内部可变别名。

    转换通过 A V = B I 直接求解,避免把理想直通强行转成不存在的有限 Z。逆矩阵使用行尺度选主元;相对主元阈值 1e-12,奇异/不可靠或溢出时拒绝,不加任意扰动。端口选择将被删除端口接其匹配负载,不是开路删除。插值是当前表示的实虚部分段线性,禁止外推。级联必须频率网格与连接参考阻抗一致;传输链要求非零 S21,去嵌入还要求夹具可逆。

    diagnostics 检查 I-SᴴS,返回 passive/active/boundary 容差带,不能只看各列功率或单个 S 元素。群时延采用相位展开和中心割线/端点单边差分,单位秒;两个相邻采样间真实相位变化超过 π 时无法唯一恢复。零传输的相位没有定义,会报错。

    损耗的精确零幅度对应正无穷 dB,不伪造有限数值;JSON 省略该数值并设置 *_infinite。VSWR 对单位反射为 infinite,对幅度大于 1 为 active/未定义。普通缺失字段不能解释为 0。

    #明确边界

    • 1–32 端口,最多 100000 频点、2000000 复数;输入/输出文本最大 64 MB,最多 1000000 行,单行 262144 字符,引用/模态头 4096 字符,信息段 1000000 字符。超限明确拒绝;这是数据限额,不是恒定进程内存承诺。
    • 正实且随频率不变的参考阻抗;不支持复杂/频变参考阻抗、稀疏/二进制扩展。仅有限 double 数字;DB 不编码精确零,需 RI/MA。
    • 保存混合模的文件原始矩阵与单端参考电阻,不假装完成单端/混合模变换。network_only() 对噪声或混合模拒绝,raw_network() 是有文档警示的显式低层访问。CLI 没有隐藏的“忽略元数据”开关。
    • 信息段可保存,注释及原始排版不会保存;legacy 写出拒绝信息段以免静默丢弃。噪声仅二端口。分析不宣称全频因果性、完整 RF 仿真或实测设备认证。

    #验证与来源

    moon fmt --check moon info node tools/check-cli.mjs python -m pip install -r tools/requirements.txt python tools/verify-reference.py python tools/verify-bands.py

    独立开发参考为 IBIS Touchstone 2.1 规范 和 scikit-rf。没有复制规范全文或把 Python 库包装成实现。详细独立检查、容差、工作量测量及源码散列见 TESTING 和 reference.json。

    验证工具许可证、参考范围和合成样例来源见 SOURCES。

    0.1.x频段功能的历史源码绑定记录见 bands-20260922.json;原 reference.json 保留为增强前的历史证据,不能用其旧散列证明新源码。

    查重刷新于 2026-09-22:Mooncakes kw=touchstone 与 GitHub touchstone language:MoonBit 均未命中,并检查公开网页索引;此结论限检索范围,不宣称全球不存在。

    0.2.0当前通带波动与厂商输入对照见 reassessment-20260927/LOCAL-CHECKS.json 及 PUBLIC-SAMPLE;旧bands回执不证明本轮新增代码。

    #本地验收与公开交付(2026-09-28)

    核心实现使用 MoonBit;固定编译器为 moonc 0.10.14+7d59c7ec9。先按本文安装宿主依赖、运行 moon update,再从仓库根目录执行以下与 CI 对齐的检查;可运行任务和适用边界见本文前面的示例与说明。

    moon check --target all --deny-warn moon test --target js --deny-warn moon test --target wasm-gc --deny-warn moon build --target js --release --deny-warn moon package

    跨平台复核(2026-09-28,本地 Ubuntu-D 26.04 WSL2):从当时的源码归档全新解包,固定 moonc 0.10.14+7d59c7ec9 下通过 moon update、moon fmt --check、moon info、严格检查、JS/Wasm-GC 测试及 JS release 构建;Node 24.21.0 跑通本仓一条宿主入口。本次补记仅修改文档,代码与 CI 未变;复核日志在本地交接包中,公开提交后的 GitHub Actions 仍须单独核对。

    本地核验:JS/Wasm-GC 各 25 项测试、CLI/频段示例和独立 RF 公开数据核对通过。 moon package 已完成离线打包预检,它不等于已发布到 Mooncakes。

    公开交付(2026-09-28 核对):尚无本项目正式公开仓库 URL 或 Mooncakes 版本;模块名 guolei-dev/touchstone 是拟交付账号形式的本地名称,正式发布前须核实账号归属和发布权限;换题资格、仓库、公开 CI 和首次发布均待团队办理,不能沿用旧题仓库链接。相关远端 CI 与赛事结果仍需以实际记录核对。项目许可见 LICENSE;如使用第三方材料,其来源和许可见仓内相应说明。

    BandIssue

    pub struct BandIssue {
    observation : BandObservation
    reason : String
    } derive(ToJson)

    reason: below_minimum, above_maximum, or undefined. Issues are ordered by rule then original frequency; they are NOT contiguous violation intervals.

    BandIssue::to_json

    fn BandIssue::to_json(BandIssue) -> Json

    BandLimit

    pub struct BandLimit {
    metric : BandMetric
    start_hz : Double
    end_hz : Double
    input : Int
    output : Int
    minimum : Double?
    maximum : Double?
    }

    A validated, immutable rule over a CLOSED frequency interval in Hz. Limits are inclusive, finite and exact (no implicit tolerance).

    BandMetric

    pub(all) enum BandMetric {
    ReflectionMagnitude
    TransmissionMagnitude
    ReturnLossDb
    InsertionLossDb
    Vswr
    }

    Scalar S-parameter metrics. Losses use -20 log10(|S|), not power dB. Reflection/return loss/VSWR use S[input,input]; transmission/insertion loss use S[output,input]. Other ports are matched at their real references.

    BandMetric::name

    fn BandMetric::name(self : BandMetric) -> String

    BandObservation

    pub struct BandObservation {
    point : Int
    frequency_hz : Double
    value : Double?
    state : String
    }

    Source index is zero-based in the ORIGINAL network, not the selected band. state: finite, infinite (+infinity), or undefined (active-reflection VSWR). JSON value is an explicit number/null, never a one-element Option array.

    BandObservation::to_json

    fn BandObservation::to_json(self : BandObservation) -> Json

    BandReport

    pub struct BandReport {
    scope : String
    status : String
    reference_ohms : Array[Double]
    max_details : Int
    results : Array[BandResult]
    } derive(ToJson)

    Aggregate sampled-only verdict, preserving the original reference ohms. fail if ANY rule fails; else inconclusive if ANY rule is inconclusive. Details share ONE global max_details budget, consumed in rule/point order.

    BandReport::issues_csv

    fn BandReport::issues_csv(self : BandReport) -> String

    Export retained issues only. An empty CSV is NOT a pass certificate: use the JSON report for uncovered bands, counts, verdicts and truncation flags. All strings are fixed library identifiers; no user text enters CSV cells.

    BandReport::to_json

    fn BandReport::to_json(BandReport) -> Json

    BandResult

    pub struct BandResult {
    rule : BandLimit
    status : String
    range_covered : Bool
    start_sampled : Bool
    end_sampled : Bool
    sample_count : Int
    failed_count : Int
    undefined_count : Int
    lowest : BandObservation?
    highest : BandObservation?
    issues : Array[BandIssue]
    issues_truncated : Bool
    }

    pass means all selected SAMPLES passed, with nonempty selection, dataset range covering both requested edges and no undefined values. It does NOT certify the unsampled frequencies, even when both edge_sampled flags hold. fail takes precedence over inconclusive. Undefined values are not extrema. Repeated extrema retain the first original point. Counts are never capped.

    BandResult::to_json

    fn BandResult::to_json(self : BandResult) -> Json

    Complex

    pub(all) struct Complex {
    re : Double
    im : Double
    } derive(ToJson)

    Cartesian complex value. Values admitted to a Network must be finite.

    Complex::add

    fn Complex::add(a : Complex, b : Complex) -> Complex

    Complex::conjugate

    fn Complex::conjugate(a : Complex) -> Complex

    Complex::divide

    fn Complex::divide(a : Complex, b : Complex) -> Complex raise

    Scaled division avoids squaring the denominator. Overflow is reported, never silently installed in a network. Zero is not replaced by an epsilon.

    Complex::magnitude

    fn Complex::magnitude(a : Complex) -> Double

    Complex::mul

    fn Complex::mul(a : Complex, b : Complex) -> Complex

    Complex::phase

    fn Complex::phase(a : Complex) -> Double

    Complex::scale

    fn Complex::scale(a : Complex, x : Double) -> Complex

    Complex::sub

    fn Complex::sub(a : Complex, b : Complex) -> Complex

    Complex::to_json

    fn Complex::to_json(Complex) -> Json

    Diagnostic

    pub(all) struct Diagnostic {
    frequency_hz : Double
    reciprocity_error : Double
    reciprocal : Bool
    passivity : String
    } derive(ToJson)

    Pointwise diagnostics, not a broadband causality or physical certification. passive: I-S^H S-tolI is positive definite; active: I-S^H S+tolI is not positive definite; boundary: the tolerance band between them.

    Diagnostic::to_json

    fn Diagnostic::to_json(Diagnostic) -> Json

    Document

    pub struct Document {
    // private fields
    }

    File metadata is separate from a Network so a transformation cannot silently pretend that unchanged noise or mixed-mode metadata describe a new network.

    Document::information_lines

    fn Document::information_lines(self : Document) -> Array[String]

    Document::mixed_mode_order

    fn Document::mixed_mode_order(self : Document) -> Array[String]

    Document::network_only

    fn Document::network_only(self : Document) -> Network raise

    Extract an ordinary network only when no noise/mode metadata would be lost.

    Document::noise_data

    fn Document::noise_data(self : Document) -> Array[NoisePoint]

    Document::noise_reference_ohms

    fn Document::noise_reference_ohms(self : Document) -> Double

    Document::raw_network

    fn Document::raw_network(self : Document) -> Network

    Explicitly access raw matrices. For mixed mode, indices denote descriptors, and references remain SINGLE-ENDED port references, not effective mode Z0.

    Document::write

    fn Document::write(self : Document) -> String raise

    Canonical 2.1 output in Hz/RI/full/12_21 form; preserves noise, mode order, information and references. Comments and original whitespace are not kept.

    Network

    pub struct Network {
    // private fields
    }

    Opaque owned network. Matrices are row-major and frequencies are Hz. Parameters are S (dimensionless), Z (ohm), Y (siemens), or two-port H/G. H/G diagonal units differ; file normalization is resolved by the parser.

    Network::cascade

    fn Network::cascade(self : Network, right : Network) -> Network raise

    Cascade this two-port followed by right. Connected references must match. Requires nonzero forward transmission; unilateral reverse transmission is OK.

    Network::check_bands

    fn Network::check_bands(self : Network, limits : Array[BandLimit], max_details? : Int) -> BandReport raise

    Check 1..256 rules on ORIGINAL sampled frequencies only. No interpolation, extrapolation, resampling, or automatic reference renormalization. Convert the network to S once; a singular conversion fails the whole operation. Limit total selected point/rule evaluations to 2,000,000. Detail budget is 0..100000 (default 256); truncation never changes verdicts/counts/extrema.

    Network::check_ripple

    fn Network::check_ripple(self : Network, start_hz : Double, end_hz : Double, input~ : Int, output~ : Int, maximum_ripple_db~ : Double) -> RippleReport raise

    Check max(loss)-min(loss), loss = -20log10(abs(S[output,input])). Inclusive maximum, no interpolated endpoints and no implicit tolerance. At least TWO measured points are required even for an equal-edge band. Uncovered ranges cannot pass, but an observed violation always fails. One zero transmission and one nonzero transmission imply infinite ripple; all-zero transmission is undefined (infinity-infinity), never a falsely perfect flat passband.

    Network::convert

    fn Network::convert(self : Network, parameter : String) -> Network raise

    Convert S/Y/Z/H/G directly; H/G require two ports. References remain fixed. Ill-conditioned destination equations raise; no diagonal jitter is added.

    Network::deembed

    fn Network::deembed(self : Network, left : Network, right : Network) -> Network raise

    measured = left * DUT * right. Return DUT using fixture inner references. Fixture chain matrices must be invertible and grids exactly equal.

    Network::diagnostics

    fn Network::diagnostics(self : Network, tolerance? : Double) -> Array[Diagnostic] raise

    Power-wave diagnostics at real references. Tolerance is absolute on the dimensionless Hermitian form and on S-S^T; must be in (0, 0.01].

    Network::frequency_hz

    fn Network::frequency_hz(self : Network) -> Array[Double]

    Network::group_delay

    fn Network::group_delay(self : Network, output : Int, input : Int) -> Array[Double] raise

    Group delay in seconds: -d unwrap(arg S[out,in])/d(2pif). Uses centered secants internally and one-sided endpoints, including uneven grids. Requires >=2 points and nonzero transmission throughout. Adjacent true phase changes >=pi cannot be recovered from sampled data unambiguously.

    Network::interpolate

    fn Network::interpolate(self : Network, frequency_hz : Array[Double]) -> Network raise

    Piecewise linear interpolation in the current complex Cartesian parameters. No extrapolation, phase fitting, noise interpolation or causality claim.

    Network::matrix

    fn Network::matrix(self : Network, point : Int) -> Array[Complex] raise

    Obtain an independent matrix copy; point is a zero-based frequency index.

    Network::metrics

    fn Network::metrics(self : Network, output : Int, input : Int) -> Array[SignalMetric] raise

    Return loss/VSWR at input and insertion loss from input to output. Ports are zero-based. All other ports are matched to their reference.

    Network::parameter

    fn Network::parameter(self : Network) -> String

    Network::ports

    fn Network::ports(self : Network) -> Int

    Network::reference_ohms

    fn Network::reference_ohms(self : Network) -> Array[Double]

    Network::renormalize

    fn Network::renormalize(self : Network, reference_ohms : Array[Double]) -> Network raise

    Return S parameters at new positive real per-port reference impedances. Uses direct wave equations, including ideal throughs where Z is undefined.

    Network::select_ports

    fn Network::select_ports(self : Network, ports : Array[Int]) -> Network raise

    Select/reorder zero-based ports, terminating omitted ports in their own matched reference impedances. Output is S; this is not an open-circuit cut.

    Network::write_legacy

    fn Network::write_legacy(self : Network, format? : String, unit? : String) -> String raise

    Write 1.0 (uniform reference) or 1.1 (per-port references), preserving SI values by reversing legacy normalization. No metadata exists on Network.

    NoisePoint

    pub(all) struct NoisePoint {
    frequency_hz : Double
    minimum_figure_db : Double
    gamma_opt : Complex
    resistance_ohms : Double
    } derive(ToJson)

    Noise figure is dB, gamma_opt is a complex reflection coefficient relative to Document::noise_reference_ohms, and resistance is always physical ohm.

    NoisePoint::to_json

    fn NoisePoint::to_json(NoisePoint) -> Json

    RippleReport

    pub struct RippleReport {
    scope : String
    status : String
    start_hz : Double
    end_hz : Double
    input : Int
    output : Int
    maximum_ripple_db : Double
    sample_count : Int
    range_covered : Bool
    start_sampled : Bool
    end_sampled : Bool
    ripple_db : Double?
    ripple_state : String
    lowest_loss : BandObservation?
    highest_loss : BandObservation?
    }

    Peak-to-peak insertion loss over sampled frequencies, in dB. This is an aggregate constraint: every point can satisfy an absolute maximum yet the difference between the lowest and highest values can violate a ripple limit. Witness point indices refer to the original input network.

    RippleReport::to_json

    fn RippleReport::to_json(self : RippleReport) -> Json

    SignalMetric

    pub(all) struct SignalMetric {
    frequency_hz : Double
    reflection_magnitude : Double
    transmission_magnitude : Double
    return_loss_db : Double?
    return_loss_infinite : Bool
    insertion_loss_db : Double?
    insertion_loss_infinite : Bool
    vswr : Double?
    vswr_state : String
    } derive(ToJson)

    None dB with its infinite flag means exact zero magnitude (+infinity dB). VSWR is None for unit reflection (infinite) or active reflection (undefined). ToJson omits None-valued fields; state/flag fields are always present.

    SignalMetric::to_json

    band_limit

    fn band_limit(metric : BandMetric, start_hz : Double, end_hz : Double, input~ : Int, output~ : Int, minimum? : Double, maximum? : Double) -> BandLimit raise

    At least one bound is required. Equal frequency edges permit a single-point check. Port existence is additionally checked against the target network.

    complex

    fn complex(re : Double, im? : Double) -> Complex

    document

    fn document(data : Network, noise? : Array[NoisePoint], noise_reference_ohms? : Double, mixed_mode_order? : Array[String], information? : Array[String]) -> Document raise

    Construct a file document with owned metadata. Noise must be an increasing two-port series whose first frequency is <= the final network frequency.

    invert_matrix

    fn invert_matrix(n : Int, matrix : Array[Complex]) -> Array[Complex] raise

    Invert a finite row-major complex matrix by scaled partial pivoting. Reject near-singular pivots relative to their row scale (1e-12). Returns a new array. This is a numerical guard, not a condition estimator.

    multiply_matrices

    fn multiply_matrices(n : Int, a : Array[Complex], b : Array[Complex]) -> Array[Complex] raise

    Multiply finite row-major square matrices. Neither input is mutated.

    network

    fn network(ports : Int, frequencies : Array[Double], values : Array[Array[Complex]], reference : Array[Double], parameter? : String) -> Network raise

    Copy and validate inputs: 1..32 ports, positive real references, nonnegative strictly increasing frequencies, at most 100000 points / 2000000 values.

    parse

    fn parse(text : String, ports? : Int) -> Document raise

    Read a 1.x or 2.0/2.1 document. Legacy requires explicit ports. For 2.x a supplied ports value is a consistency check, never an override of the file.

    parse_legacy

    fn parse_legacy(text : String, ports~ : Int) -> Network raise

    Read legacy 1.0/1.1 network data; ports must come from a trusted filename or explicit caller choice. Frequencies become Hz, non-S values become SI. Noise sections are not accepted by this network-only entry point.