跳到主要内容Python 驱动的 ADS 自动化仿真框架与 API 实战指南 | 极客日志Python算法
Python 驱动的 ADS 自动化仿真框架与 API 实战指南
ADS 仿真流程繁琐,手动调整参数效率低。本文分享一套基于 Python 的自动化仿真框架,封装了环境配置、参数更新、批量运行及结果提取全流程。采用模板方法模式,将通用逻辑与电路特定参数更新解耦,支持 JSON/CSV 数据输入,自动处理复数与数组结果序列化。结合事务机制确保参数修改原子性,内置异常捕获与统计功能,显著提升射频电路参数扫描与优化效率。
Python 驱动的 ADS 自动化仿真框架与 API 实战指南
1. 自动化数据提取工具库详解
为了降低 ADS 仿真的程控开发门槛,我设计了一套通用的自动化工具库 auto_simulator.py。它封装了从环境配置、参数更新、仿真运行到结果提取的全流程,让开发者只需关注'如何将参数应用到电路'这一核心逻辑。
"""
通用自动化仿真模块
提供通用的 ADS 仿真自动化框架,支持批量参数扫描和结果提取。
用户只需实现参数更新接口即可使用。
"""
import pandas as pd
import numpy as np
import json
import os
from pathlib import Path
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional, Callable
from keysight.ads.de import db_uu as db
from keysight.ads import dataset
import utils
class ParameterUpdater(ABC):
"""参数更新抽象接口"""
@abstractmethod
def update_parameters(self, design: db.Design, param_row: pd.Series) -> None:
"""
更新电路参数
Args:
design: ADS 设计对象
param_row: 包含参数值的 pandas Series
"""
pass
class AutoSimulator:
"""通用自动化仿真器"""
def __init__(self, parameter_updater: ParameterUpdater):
.parameter_updater = parameter_updater
.design =
"""
初始化仿真器
Args:
parameter_updater: 参数更新器实例
"""
self
self
None
AutoSimulator 采用了 模板方法模式 (Template Method Pattern) 进行设计:
- 不变的部分:ADS 工作空间的打开/关闭、仿真器的实例化、结果文件的遍历与读取、异常处理、数据保存(追加到原始 JSON)。这些通用逻辑被封装在基类和工具函数中。
- 变化的部分:不同的电路有不同的参数需要更新(如电阻值、信号文件路径、偏置电压)。这部分逻辑通过
ParameterUpdater 接口暴露给用户实现。
1.2 核心类说明
utils.auto_simulator.py 中主要包含两个核心类:ParameterUpdater 和 AutoSimulator。
1. ParameterUpdater (抽象基类)
设计意图:这是一个策略接口(Strategy Pattern),将'如何更新电路参数'这一变化点从框架中剥离出来。由于每个 ADS 设计的元件名称、变量控件结构、参数命名规则都不同,这部分逻辑无法通用化,必须由用户根据自己的电路原理图来实现。
| 方法签名 | update_parameters(self, design: db.Design, param_row: pd.Series) -> None |
|---|
| 调用时机 | AutoSimulator 在处理每一行参数样本时,会在执行仿真前调用此方法 |
| design 参数 | 由 setup_ads_environment 打开的设计对象,类型为 keysight.ads.de.db_uu.Design。通过它可以访问原理图中的所有元件实例 |
| param_row 参数 | Pandas Series 对象,键为参数名(如 "R1", "Vbias"),值为参数数值。这些键名对应输入 JSON/CSV 文件中的列名 |
| 返回值 | 无(None)。参数更新通过直接修改 design 对象完成 |
from keysight.ads.de.db import Transaction
with Transaction(design) as transaction:
inst1.vars.update({...})
inst2.parameters["C"].value = "10 pF"
transaction.commit()
inst.update_item_annotation()
更新元件参数(用于电阻、电容、信号源等具体元件):
inst.parameters["参数名"].value = "值(字符串,可带单位)"
- 所有参数值必须转换为字符串格式,ADS 内部会自行解析单位。
- 文件路径类参数(如
DAC1 的 File 参数)需要用双引号包裹,即 "'path'"。
- 若更新多个元件,建议包裹在
Transaction 中,确保要么全部成功、要么全部回滚。
- 更新后调用
update_item_annotation() 可让原理图界面同步显示新值(非必需但推荐)。
2. AutoSimulator (核心控制器)
职责:这是自动化仿真的主引擎(Driver)。它不关心电路的具体细节,只负责流程控制:加载数据 -> 设置环境 -> 循环仿真 -> 提取结果 -> 保存文件。
-
__init__(self, parameter_updater: ParameterUpdater)
构造函数,负责将用户实现的参数更新器'注册'到控制器中。内部行为是将 parameter_updater 保存为成员变量,同时初始化 self.design = None(后续由 setup_ads_environment 填充)。
-
load_parameter_samples(self, filepath: str) -> pd.DataFrame
从文件加载参数样本数据,统一转换为 Pandas DataFrame 供后续遍历使用。
支持的输入格式:
.json:使用 json.load 读取。支持三种结构:① 纯列表;② 字典包裹;③ 单条记录(自动包装为单行 DataFrame)。
.csv:调用 pd.read_csv(filepath)。
.xlsx / .xls:调用 pd.read_excel(filepath)。
注意:虽然加载支持多种格式,但目前结果保存仅实现了 JSON 格式。如需将结果回写到 CSV/Excel,用户可继承 AutoSimulator 并重写保存逻辑。
-
setup_ads_environment(self, workspace_path: str, library_name: str, design_name: str) -> None
初始化 ADS 工作环境,完成工作空间 -> 库 -> 设计的三级打开流程。
utils.get_workspace(workspace_path) —— 打开/连接工作空间
utils.get_library(workspace, library_name) —— 加载指定库
utils.get_design(library, design_name, ViewType.SCHEMATIC) —— 打开原理图视图
成功后 self.design 将指向该设计对象,后续的参数更新和仿真都基于此对象进行。
-
extract_simulation_results(self, results: dataset.Dataset, target_variables: List[str], debug: bool = True) -> Dict[str, Any]
从仿真结果数据集中智能提取用户指定的目标变量,是本工具库最核心的数据处理函数。
提取策略(不依赖硬编码的块名):
- 遍历
results.varblock_names 获取所有 VariableBlock。
- 对每个块,通过
varblock.ivars / varblock.dvars 获取其独立变量和依赖变量名列表。
- 若目标变量出现在
dvars 中,调用 varblock.to_dataframe(dvar_names=[target_var]) 精准提取该列。
- 根据数据长度自动判断类型:长度=1 转为标量 float,长度>1 转为向量 list。
-
run_batch_simulation(...)
批量仿真的主入口,串联整个自动化流程。大多数情况下用户只需调用此方法。
执行流程:
- 加载数据:调用
load_parameter_samples(data_filepath) 读取参数表。
- 限制样本:若设置了
max_samples,取前 N 行。
- 初始化环境:调用
setup_ads_environment(...) 打开 ADS 设计。
- 循环处理每个样本:
- 调用
parameter_updater.update_parameters(design, row) 更新电路参数。
- 删除旧仿真数据:移除
{cell_path}/data/{design_name}.ds,防止读到历史缓存。
- 调用
run_simulation_and_extract_results(target_variables) 执行仿真并提取结果。
- 若提供了
result_processor,对结果进行后处理。
- 将结果追加到列表。
- 保存结果:若
save_to_original=True,调用 _save_results_to_original_file 合并写回。
- 打印统计:调用
_print_simulation_statistics 输出成功/失败计数与数值范围。
1.3 实战示例:开发一个自定义仿真器
本节以射频放大器增益仿真为例,完整演示从数据准备到结果提取的全流程。
1.3.1 输入数据格式
首先准备参数样本文件(JSON 格式),每条记录代表一组待仿真的电路参数:
[
{"sample_id": 1, "Vbias": 3.3, "R_source": 50, "C_coupling": 100, "L_match": 2.2},
{"sample_id": 2, "Vbias": 3.0, "R_source": 75, "C_coupling": 150, "L_match": 1.8}
]
sample_id:样本编号,用于日志输出和结果追踪(可选,若不提供则自动使用行索引)。
- 其余字段:与原理图中需要更新的变量/参数一一对应。
1.3.2 实现参数更新器
根据你的 ADS 原理图结构,继承 ParameterUpdater 并实现 update_parameters 方法:
"""amplifier_simulator.py 射频放大器批量仿真脚本"""
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.append(str(project_root))
import pandas as pd
from utils.auto_simulator import AutoSimulator, ParameterUpdater
from keysight.ads.de import db_uu as db
from keysight.ads.de.db import Transaction
class AmplifierUpdater(ParameterUpdater):
"""放大器参数更新器"""
def update_parameters(self, design: db.Design, param_row: pd.Series) -> None:
"""将 param_row 中的参数值写入 ADS 原理图"""
with Transaction(design) as transaction:
inst_var = design.get_instance("VAR1")
inst_var.vars.update({
"Vbias": str(param_row["Vbias"]),
"R_source": str(param_row["R_source"])
})
inst_var.update_item_annotation()
inst_c1 = design.get_instance("C1")
inst_c1.parameters["C"].value = f"{param_row['C_coupling']} pF"
inst_c1.update_item_annotation()
inst_l1 = design.get_instance("L1")
inst_l1.parameters["L"].value = f"{param_row['L_match']} nH"
inst_l1.update_item_annotation()
transaction.commit()
- Transaction 上下文管理器:将所有修改包裹在事务中,调用
commit() 后才真正生效。
- VAR 控件 vs 元件参数:VAR 控件用
.vars.update(),普通元件用 .parameters["名称"].value。
- 单位处理:电容用
pF、电感用 nH,ADS 会自动解析。
- 字符串转换:所有值必须是字符串,使用
str() 或 f-string。
1.3.3 编写主控制脚本
def main():
"""主函数:配置参数并启动批量仿真"""
config = {
"data_filepath": "E:/project/data/amplifier_samples.json",
"workspace_path": "E:/ADS2026_wrk/MyAmplifier_wrk",
"library_name": "MyAmplifier_lib",
"design_name": "Amp_Gain_Schematic",
"target_variables": ["S[2,1]", "S[1,1]", "Gain_dB", "NF_dB"],
"max_samples": None,
}
print("=" * 60)
print("射频放大器批量仿真")
print("=" * 60)
try:
updater = AmplifierUpdater()
simulator = AutoSimulator(updater)
final_df = simulator.run_batch_simulation(
data_filepath=config["data_filepath"],
workspace_path=config["workspace_path"],
library_name=config["library_name"],
design_name=config["design_name"],
target_variables=config["target_variables"],
max_samples=config["max_samples"],
save_to_original=True,
)
print(f"\n仿真完成!共处理 {len(final_df)} 个样本")
except Exception as e:
print(f"\n❌ 仿真过程发生错误:{e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
1.3.4 输出结果格式
仿真完成后,原始 JSON 文件会被追加 simulation_results 字段:
[
{
"sample_id": 1,
"Vbias": 3.3,
"simulation_results": {
"S[2,1]": {"real": 2.51, "imag": 1.33, "magnitude": 2.84, "phase_deg": 27.9},
"Gain_dB": 9.07
}
}
]
- 复数类型(如 S 参数):自动拆分为
real、imag、magnitude、phase_deg。
- 标量类型(如
Gain_dB):直接存储数值。
- 提取失败:对应字段值为
null。
1.3.5 进阶:自定义结果后处理
若需要对原始提取结果进行计算(如将复数 S21 转为 dB),可传入 result_processor 回调:
import numpy as np
def process_results(raw_results: dict) -> dict:
processed = raw_results.copy()
s21 = raw_results.get("S[2,1]")
if s21 is not None and isinstance(s21, complex):
processed["S21_dB"] = 20 * np.log10(abs(s21))
return processed
1.4 为什么推荐使用此模式?
- 稳健性:内部统一捕获仿真异常,单个样本失败不会拖垮全流程。
- 断点续传:结果实时写回原始文件,结合简单的跳过逻辑即可实现增量仿真。
- 结果对齐:自动将输入参数与输出结果一一对应,便于后续数据分析与模型训练。
1.5 Utils 工具库封装
此前在自动化工具库中也用到了一些二次封装 ADS API 的类和方法,这里一并给出参考实现,方便理解底层交互逻辑。
import os
import keysight.ads.dataset as dataset
from keysight.ads import de
from keysight.ads.de import db_uu as db
from enum import Enum
from typing import Literal, Union
from keysight.edatoolbox import ads
class ViewType(Enum):
SCHEMATIC = "schematic"
SYMBOL = "symbol"
LAYOUT = "layout"
def get_workspace(workspace_path: str) -> de.Workspace:
if de.workspace_is_open():
de.close_workspace()
if os.path.exists(workspace_path):
print(f"Workspace directory already exists: {workspace_path}")
return de.active_workspace()
workspace = de.create_workspace(workspace_path)
workspace.open()
return workspace
def get_library(workspace: de.Workspace, name: str) -> de.Library:
assert workspace.is_open
library_path = workspace.path / name
if os.path.exists(library_path):
if de.library_is_open(name):
de.close_library(name)
return workspace.open_library(name, library_path, de.LibraryMode.SHARED)
de.create_new_library(name, library_path)
workspace.add_library(name, library_path, de.LibraryMode.SHARED)
return workspace.open_library(name, library_path, de.LibraryMode.SHARED)
def get_design(
library: de.Library,
name: str,
view_type: ViewType = ViewType.SCHEMATIC,
mode: db.DesignMode = db.DesignMode.APPEND
) -> db.Design:
design_ref = f"{library.name}:{name}:{view_type.value}"
if de.design_exists(design_ref):
return db.open_design(design_ref, mode=mode)
else:
if view_type == ViewType.SCHEMATIC:
design = db.create_schematic(design_ref)
elif view_type == ViewType.SYMBOL:
design = db.create_symbol(design_ref)
elif view_type == ViewType.LAYOUT:
design = db.create_layout(design_ref)
else:
raise ValueError(f"Invalid view type: {view_type}")
return design
class SimulationManager:
"""Complete simulation manager with temporary file cleanup and path safety handling"""
def __init__(self, design: db.Design):
self.design = design
self.output_dir = os.path.join(self.design.cell.path, "data")
self.netlist = None
self.result = None
self.result_path = os.path.join(self.output_dir, f"{self.design.cell_name}.ds")
self.managed_simulator = ManagedCircuitSimulator()
self.data_file_is_exist = os.path.exists(self.result_path)
if self.data_file_is_exist:
self.result = dataset.open(self.result_path)
def run_design_simulation(self, output_dir=None, validate_name=True, **sim_kwargs):
if output_dir is None:
output_dir = self.output_dir
os.makedirs(output_dir, exist_ok=True)
print(f"=== Running simulation: {self.design.design_name} ===")
if validate_name:
self.validate_design_name()
try:
self.netlist = self.design.generate_netlist()
if not self.netlist or len(self.netlist.strip()) == 0:
raise RuntimeError("Generated netlist is empty")
self.managed_simulator.run_netlist(
netlist=self.netlist,
output_dir=output_dir,
**sim_kwargs
)
self.data_file_is_exist = True
except Exception as e:
print(f"Simulation failed: {e}")
raise
def get_simulation_results(self) -> dataset.Dataset:
if not self.data_file_is_exist:
raise RuntimeError("Simulation has not been run or failed, cannot get results")
return dataset.open(self.result_path)
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online