dsh-plugin-pscad-expert-vyma 1.0.16 → 1.0.17

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pscad-dsh-plugin — PSCAD 自动化建模与仿真控制专家插件
2
2
 
3
- > **版本**:1.0.16 | **注册名**:`pscad-automation-expert`
3
+ > **版本**:1.0.17 | **注册名**:`pscad-automation-expert`
4
4
  > **来源**:PSCAD 知识资产固化项目(四阶段:技能文档 → 模板索引 → 工具库 → 插件打包)
5
5
  > 所有知识、工具、模板均来自真实仿真验证(L13~L19 认证轮 + R4 科研轮),**无伪造数据**。
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-pscad-expert-vyma",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "PSCAD 自动化建模与仿真控制专家插件(DSH Agent 工具库 + 基准模板 + 领域知识)",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/plugin.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  # 由知识资产固化项目自动生成(2026-08-25)· 部署至 Git 仓库
4
4
  # ============================================================
5
5
  name: pscad-automation-expert
6
- version: 1.0.16
6
+ version: 1.0.17
7
7
  description: PSCAD 自动化建模与仿真控制专家插件
8
8
 
9
9
  # ---- 系统提示词(加载到 Agent 上下文最前置位置)----
@@ -51,6 +51,7 @@ templates:
51
51
  - gfm_island_droop_nose.pscx # 双 GFM 孤岛下垂 + 鼻点(历史经典)
52
52
  - three_phase_cable_fault.pscx # 三相电缆故障(Cable_Coax 专项)
53
53
  - tmmb_norton_element.pscx # TMMB 热磁忆阻 Norton 等效自定义元件基准(v6 三路验证)
54
+ - _blank.pscx # 最小空白工程模板(create_case 强落盘兜底)
54
55
 
55
56
  # ---- 运行环境(无硬编码:路径由 init_pscad_environment 动态配置于运行时)----
56
57
  runtime:
@@ -0,0 +1,124 @@
1
+ <project name="blank_probe_case" version="5.0.0" schema="" Target="EMTDC">
2
+ <paramlist name="Settings">
3
+ <param name="creator" value="Administrator,1788021869" />
4
+ <param name="time_duration" value="0.5" />
5
+ <param name="time_step" value="50" />
6
+ <param name="sample_step" value="250" />
7
+ <param name="chatter_threshold" value=".001" />
8
+ <param name="branch_threshold" value=".0005" />
9
+ <param name="StartType" value="0" />
10
+ <param name="startup_filename" value="$(Namespace).snp" />
11
+ <param name="PlotType" value="0" />
12
+ <param name="output_filename" value="$(Namespace).out" />
13
+ <param name="SnapType" value="0" />
14
+ <param name="SnapTime" value="0.3" />
15
+ <param name="snapshot_filename" value="$(Namespace).snp" />
16
+ <param name="MrunType" value="0" />
17
+ <param name="Mruns" value="1" />
18
+ <param name="Scenario" value="" />
19
+ <param name="Advanced" value="14335" />
20
+ <param name="sparsity_threshold" value="200" />
21
+ <param name="Options" value="16" />
22
+ <param name="Build" value="18" />
23
+ <param name="Warn" value="0" />
24
+ <param name="Check" value="0" />
25
+ <param name="description" value="" />
26
+ <param name="revisor" value="Administrator, 1788021869" />
27
+ </paramlist>
28
+ <Layers />
29
+ <List classid="Settings" />
30
+ <bookmarks />
31
+ <GlobalSubstitutions name="Default">
32
+ <List classid="Sub" />
33
+ <List classid="ValueSet" />
34
+ <paramlist>
35
+ <param name="Current" value="" />
36
+ </paramlist>
37
+ </GlobalSubstitutions>
38
+ <definitions>
39
+ <Definition classid="StationDefn" name="DS" id="2105821578" group="" url="" version="" build="" crc="483640" view="false">
40
+ <paramlist name="">
41
+ <param name="Description" value="" />
42
+ </paramlist>
43
+ <schematic classid="StationCanvas">
44
+ <paramlist>
45
+ <param name="show_grid" value="0" />
46
+ <param name="size" value="0" />
47
+ <param name="orient" value="1" />
48
+ <param name="show_border" value="0" />
49
+ <param name="monitor_bus_voltage" value="0" />
50
+ <param name="show_signal" value="0" />
51
+ <param name="show_virtual" value="0" />
52
+ <param name="show_sequence" value="0" />
53
+ <param name="auto_sequence" value="1" />
54
+ <param name="bus_expand_x" value="8" />
55
+ <param name="bus_expand_y" value="8" />
56
+ <param name="bus_length" value="4" />
57
+ </paramlist>
58
+ <grouping />
59
+ <Wire classid="Branch" id="2138248653" name="Main" x="180" y="180" w="66" h="82" orient="0" disable="false" defn="Main" recv="-1" send="-1" back="-1">
60
+ <vertex x="0" y="0" />
61
+ <vertex x="0" y="18" />
62
+ <vertex x="54" y="54" />
63
+ <vertex x="54" y="72" />
64
+ <User classid="UserCmp" id="1245623415" name="blank_probe_case:Main" x="0" y="0" w="0" h="0" z="-1" orient="0" defn="blank_probe_case:Main" link="-1" q="4" disable="false">
65
+ <paramlist name="" link="-1" crc="4361682" />
66
+ </User>
67
+ </Wire>
68
+ </schematic>
69
+ </Definition>
70
+ <Definition classid="UserCmpDefn" name="Main" id="537127987" group="" url="" version="" build="" crc="17500639" view="false" date="0">
71
+ <paramlist name="">
72
+ <param name="Description" value="" />
73
+ </paramlist>
74
+ <form name="" w="320" h="400" splitter="60" />
75
+ <graphics viewBox="-200 -200 200 200" size="2">
76
+ <Gfx classid="Graphics.Rectangle" id="259254034" x="-36" y="-36" w="72" h="72">
77
+ <paramlist>
78
+ <param name="color" value="Black" />
79
+ <param name="dasharray" value="0" />
80
+ <param name="thickness" value="0" />
81
+ <param name="port" value="" />
82
+ <param name="fill_style" value="0" />
83
+ <param name="fill_fg" value="Black" />
84
+ <param name="fill_bg" value="Black" />
85
+ <param name="cond" value="true" />
86
+ </paramlist>
87
+ </Gfx>
88
+ <Gfx classid="Graphics.Text" id="1721975208" x="0" y="0">
89
+ <paramlist>
90
+ <param name="text" value="%:Name" />
91
+ <param name="anchor" value="0" />
92
+ <param name="full_font" value="Tahoma, 10pt" />
93
+ <param name="angle" value="0" />
94
+ <param name="color" value="Black" />
95
+ <param name="cond" value="true" />
96
+ </paramlist>
97
+ </Gfx>
98
+ </graphics>
99
+ <schematic classid="UserCanvas">
100
+ <paramlist>
101
+ <param name="show_grid" value="0" />
102
+ <param name="size" value="0" />
103
+ <param name="orient" value="1" />
104
+ <param name="show_border" value="0" />
105
+ <param name="monitor_bus_voltage" value="0" />
106
+ <param name="show_signal" value="0" />
107
+ <param name="show_virtual" value="0" />
108
+ <param name="show_sequence" value="0" />
109
+ <param name="auto_sequence" value="1" />
110
+ <param name="bus_expand_x" value="8" />
111
+ <param name="bus_expand_y" value="8" />
112
+ <param name="bus_length" value="4" />
113
+ </paramlist>
114
+ <grouping />
115
+ </schematic>
116
+ </Definition>
117
+ </definitions>
118
+ <List classid="Resource" />
119
+ <hierarchy>
120
+ <call link="371607758" name="blank_probe_case:DS" z="-1" view="false" instance="0">
121
+ <call link="1245623415" name="blank_probe_case:Main" z="-1" view="true" instance="0" />
122
+ </call>
123
+ </hierarchy>
124
+ </project>
package/tools/main.py CHANGED
@@ -55,6 +55,7 @@ from __future__ import annotations
55
55
  import json
56
56
  import os
57
57
  import re
58
+ import shutil
58
59
  import socket
59
60
  import sys
60
61
  import time
@@ -523,6 +524,77 @@ def _gen_name(comp_type: str) -> str:
523
524
  # ---------------------------------------------------------------------------
524
525
  # 工具集 1:连接与工程加载(懒加载 mhi)
525
526
  # ---------------------------------------------------------------------------
527
+ def _walk_find(root: str, fname: str, maxdepth: int = 4) -> Optional[str]:
528
+ """深度受限目录遍历找同名文件(用于 PSCAD 安装根等大目录,取最新)。"""
529
+ if not root or not os.path.isdir(root):
530
+ return None
531
+ root = os.path.abspath(root)
532
+ best = None
533
+ try:
534
+ for dirpath, dirnames, filenames in os.walk(root):
535
+ depth = dirpath[len(root):].count(os.sep)
536
+ if depth >= maxdepth:
537
+ dirnames[:] = []
538
+ continue
539
+ dirnames[:] = [d for d in dirnames
540
+ if d not in ('__pycache__', '.git', 'node_modules')]
541
+ for fn in filenames:
542
+ if fn == fname:
543
+ p = os.path.join(dirpath, fn)
544
+ if best is None or os.path.getmtime(p) > os.path.getmtime(best):
545
+ best = p
546
+ except Exception:
547
+ pass
548
+ return best
549
+
550
+
551
+ def _search_case_file(project_name: str, folder: str) -> Optional[str]:
552
+ """广搜同名 .pscx:快路径 bases + PSCAD 安装根(深度受限),取最新。"""
553
+ import glob as _glob
554
+ bases = [folder, os.getcwd(), os.path.expanduser('~/Documents'),
555
+ os.path.expanduser('~/PSCAD'), os.path.expanduser('~/pscad_cases')]
556
+ best = None
557
+ for base in bases:
558
+ try:
559
+ hits = _glob.glob(os.path.join(base, '**', project_name + '.pscx'),
560
+ recursive=True)
561
+ except Exception:
562
+ hits = []
563
+ for h in hits:
564
+ if best is None or os.path.getmtime(h) > os.path.getmtime(best):
565
+ best = h
566
+ exe = _cfg_exe()
567
+ if exe:
568
+ root = os.path.dirname(os.path.dirname(os.path.abspath(exe)))
569
+ found = _walk_find(root, project_name + '.pscx')
570
+ if found and (best is None or os.path.getmtime(found) > os.path.getmtime(best)):
571
+ best = found
572
+ return best
573
+
574
+
575
+ def _materialize_blank(project_name: str, folder: str) -> Optional[str]:
576
+ """强落盘兜底:内置空白模板复制为 folder/name.pscx 并改写工程名。
577
+
578
+ 覆盖 mhi 2.9.7 + PSCAD 5.0.0(create_case 仅内存、save/save_as 均不可用)
579
+ 等任何组合,保证工程一定有磁盘落盘文件。
580
+ """
581
+ try:
582
+ blank = os.path.join(os.path.dirname(os.path.dirname(
583
+ os.path.abspath(__file__))), 'templates', '_blank.pscx')
584
+ if not os.path.isfile(blank):
585
+ return None
586
+ target = os.path.join(folder, project_name + '.pscx')
587
+ with open(blank, encoding='utf-8') as fh:
588
+ text = fh.read()
589
+ text = re.sub(r'<project name="[^"]*"',
590
+ '<project name="%s"' % project_name, text, count=1)
591
+ with open(target, 'w', encoding='utf-8') as fh:
592
+ fh.write(text)
593
+ return target if os.path.isfile(target) else None
594
+ except Exception:
595
+ return None
596
+
597
+
526
598
  def _persist_case(prj: Any, project_name: str,
527
599
  folder: str) -> Tuple[Optional[str], List[str]]:
528
600
  """尽力把会话内新建工程落盘,返回 (实际 .pscx 路径或 None, 尝试记录)。
@@ -560,27 +632,17 @@ def _persist_case(prj: Any, project_name: str,
560
632
  return p, attempts
561
633
  except Exception:
562
634
  continue
563
- # ③ 候选目录搜最近生成的同名文件(含 PSCAD 常见案例目录)
564
- import glob as _glob
565
- bases = [folder, os.getcwd(), os.path.expanduser('~/Documents'),
566
- os.path.expanduser('~/PSCAD'),
567
- os.path.expanduser('~/pscad_cases')]
568
- best = None
569
- for base in bases:
635
+ # ③ 广搜同名文件(含 PSCAD 安装根):找到则复制到目标目录(等效落盘)
636
+ found = _search_case_file(project_name, folder)
637
+ if found:
570
638
  try:
571
- hits = _glob.glob(os.path.join(base, '**',
572
- project_name + '.pscx'),
573
- recursive=True)
574
- except Exception:
575
- hits = []
576
- for h in hits:
577
- if best is None or os.path.getmtime(h) > os.path.getmtime(best):
578
- best = h
579
- if best:
580
- attempts.append('目录搜索命中: %s' % best)
581
- else:
582
- attempts.append('目录搜索无命中(bases: %s)' % '; '.join(bases))
583
- return best, attempts
639
+ shutil.copy2(found, expected)
640
+ attempts.append('找到并复制到目标目录: %s -> %s' % (found, expected))
641
+ return expected, attempts
642
+ except Exception as exc:
643
+ attempts.append('复制失败: %s' % exc)
644
+ attempts.append('目录搜索无命中')
645
+ return None, attempts
584
646
  except Exception as exc:
585
647
  attempts.append('整体异常: %s' % exc)
586
648
  return None, attempts
@@ -652,6 +714,17 @@ def connect_and_load_project(project_name: str,
652
714
  _pscad.create_case(project_name, folder=folder)
653
715
  prj = _pscad.project(project_name)
654
716
  pscx_path, save_attempts = _persist_case(prj, project_name, folder)
717
+ if not pscx_path:
718
+ # 强落盘兜底:内置空白模板复制为 name.pscx 再 load(任何组合都持久化)
719
+ pscx_path = _materialize_blank(project_name, folder)
720
+ if pscx_path:
721
+ try:
722
+ _pscad.load(pscx_path.replace('\\', '/'))
723
+ prj = _pscad.project(project_name)
724
+ save_attempts.append(
725
+ '强落盘兜底:内置空白模板 -> %s' % pscx_path)
726
+ except Exception as exc:
727
+ save_attempts.append('强落盘兜底 load 失败: %s' % exc)
655
728
  if pscx_path:
656
729
  folder = os.path.dirname(pscx_path) # 会话状态跟随真实落盘目录
657
730
  _prj_folder = folder
@@ -1564,232 +1637,3 @@ def self_check() -> Dict[str, Any]:
1564
1637
  })
1565
1638
  except Exception as exc:
1566
1639
  return _err(exc)
1567
-
1568
-
1569
- # ---------------------------------------------------------------------------
1570
- # TMMB 热磁忆阻分支 / 通用 Norton 自定义元件(v6:CCBR/GEQ 零重构范式)
1571
- # 实证依据见 knowledge/emtdc_custom_element_norton.md;四案例三路验证全通过。
1572
- # ---------------------------------------------------------------------------
1573
- _NORTON_DEF_NAME: str = 'TMMB'
1574
-
1575
-
1576
- def _norton_definition_xml(rmin: float, k: float, alpha: float,
1577
- beta: float, l0: float,
1578
- picard: int = 0) -> str:
1579
- """生成 v6 型 Norton 自定义元件定义 XML(TMMB 方程参数化)。
1580
-
1581
- 实现要点(全部实证):
1582
- - Branch 段 ``BRR = $NA $NB BREAKER 1.0E6``(可切换分支,seriesrl2 范式)。
1583
- - TIMEZERO(#BEGIN):``GEQ($BRR,$SS)=G0`` 固定 + ``DEFRDBR=.TRUE.``
1584
- → 导纳矩阵逐拍恒不变(零重构);G0=1/(2·RMIN+2·L0/Δt)。
1585
- - 每拍仅更新右端项 ``CCBR = I_hist + (G-G0)*V``(RHS 永不触发重构)。
1586
- - 历史电流用 seriesrl2 官方递推(不读 CBR——DEFRDBR 分支的 CBR 读回
1587
- 符号不可靠,实测陷阱);状态功率用 Norton 自洽电流 I=G*V+I_hist。
1588
- - 状态存储 STORF(NSTORF+1..8) + NSTORF+=8(误写 STORI 会覆盖相邻组件);
1589
- #LOCAL 每行一条(合并行被生成器丢弃致隐式类型陷阱);
1590
- #BEGIN 无 emtstor.h 不能访问 STORF,状态初始化放主段 IF(TIMEZERO)。
1591
- Args:
1592
- rmin/k/alpha/beta: TMMB 方程参数(R(w)=RMIN+K·w²,dw/dt=α·v·i−β·w)。
1593
- l0: 串联电感 [H](≥0;完整伴生 G=1/(R+2L/Δt))。
1594
- picard: DSDYN 内 Picard 迭代次数(0/2/3;闭合 W↔R 环,默认 0)。
1595
- Returns:
1596
- 定义 XML 字符串。
1597
- """
1598
- pic = ''
1599
- if picard >= 2:
1600
- pic = (
1601
- ' DO IVD1_1 = 1, %d\n'
1602
- ' R = $RMIN + $K * W * W\n'
1603
- ' G = 1.0/(R + 2.0*$L0/DELT)\n'
1604
- ' I = G*V + IH\n'
1605
- ' W = (W*(1.0 - DTB) + $ALPHA*DELT*(V*I)) / (1.0 + DTB)\n'
1606
- ' END DO\n' % int(picard))
1607
- return '''<Definition classid="UserCmpDefn" name="TMMB" url="" view="false" instances="0" date="0" crc="0" id="%d" group="R4">
1608
- <paramlist>
1609
- <param name="Description" value="Norton Element (CCBR/GEQ, zero-refactor)" />
1610
- </paramlist>
1611
- <form name="TMMB" w="320" h="460" splitter="70" commentlines="4">
1612
- <category name="Configuration" visible="true">
1613
- <parameter name="Name" desc="Name for Identification" type="Text" group="" helpmode="Overwrite" allowemptystr="true">
1614
- <value><![CDATA[]]></value>
1615
- <cond type="Enable"><![CDATA[true]]></cond>
1616
- </parameter>
1617
- <parameter type="Text" name="COM" desc="Fortran Comment" group="" helpmode="Overwrite" allowemptystr="false">
1618
- <value><![CDATA[NORTON]]></value>
1619
- <cond type="Enable" />
1620
- </parameter>
1621
- <cond><![CDATA[true]]></cond>
1622
- </category>
1623
- <category name="Norton Parameters" visible="true">
1624
- <parameter type="Real" name="RMIN" desc="Minimum resistance [ohm]" content_type="Variable" min="1E-9" max="1E+308" unit="ohm" group="" helpmode="Append" intent="Input" dim="1" animate="false">
1625
- <value><![CDATA[1.0]]></value>
1626
- <cond type="Enable" />
1627
- </parameter>
1628
- <parameter type="Real" name="K" desc="Memristance coefficient [ohm]" content_type="Variable" min="0" max="1E+308" unit="ohm" group="" helpmode="Append" intent="Input" dim="1" animate="false">
1629
- <value><![CDATA[1.0]]></value>
1630
- <cond type="Enable" />
1631
- </parameter>
1632
- <parameter type="Real" name="ALPHA" desc="Heating coupling [1/MW/s]" content_type="Variable" min="0" max="1E+308" unit="" group="" helpmode="Append" intent="Input" dim="1" animate="false">
1633
- <value><![CDATA[1.0]]></value>
1634
- <cond type="Enable" />
1635
- </parameter>
1636
- <parameter type="Real" name="BETA" desc="Cooling rate [1/s]" content_type="Variable" min="0" max="1E+308" unit="" group="" helpmode="Append" intent="Input" dim="1" animate="false">
1637
- <value><![CDATA[1.0]]></value>
1638
- <cond type="Enable" />
1639
- </parameter>
1640
- <parameter type="Real" name="L0" desc="Inductance [H]" content_type="Variable" min="0" max="1E+308" unit="H" group="" helpmode="Append" intent="Input" dim="1" animate="false">
1641
- <value><![CDATA[0.0]]></value>
1642
- <cond type="Enable" />
1643
- </parameter>
1644
- <cond><![CDATA[true]]></cond>
1645
- </category>
1646
- </form>
1647
- <svg viewBox="-200 -200 200 200">
1648
- <port model="Natural" name="NA" x="-72" y="0" dim="1" mode="Electrical" type="NonRemovable"><![CDATA[true]]></port>
1649
- <port model="Natural" name="NB" x="72" y="0" dim="1" mode="Electrical" type="NonRemovable"><![CDATA[true]]></port>
1650
- <port model="Transfer" name="OUT" x="0" y="-36" dim="1" mode="Output" type="Real"><![CDATA[true]]></port>
1651
- <rect x="-27" y="-15" width="54" height="30" stroke="Black" stroke-dasharray="none" stroke-width="0.2" fill-style="Hollow" fill-fg="Black" fill-bg="" />
1652
- <line x1="-72" y1="0" x2="-27" y2="0" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1653
- <line x1="27" y1="0" x2="72" y2="0" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1654
- <line x1="-12" y1="0" x2="-4" y2="0" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1655
- <line x1="-8" y1="-4" x2="-8" y2="4" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1656
- <line x1="0" y1="0" x2="8" y2="0" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1657
- <line x1="4" y1="-4" x2="4" y2="4" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1658
- <line x1="12" y1="0" x2="20" y2="0" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1659
- <line x1="16" y1="-4" x2="16" y2="4" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1660
- <line x1="0" y1="15" x2="0" y2="36" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1661
- <line x1="-5" y1="20" x2="0" y2="15" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1662
- <line x1="5" y1="20" x2="0" y2="15" stroke="Black" stroke-dasharray="none" stroke-width="0.2" />
1663
- <text x="0" y="-1" stroke="Black" fill="Black" font-size="Small" text-anchor="Middle"><![CDATA[R(w)]]></text>
1664
- <text x="0" y="13" stroke="Black" fill="Black" font-size="Small" text-anchor="Middle"><![CDATA[NRT]]></text>
1665
- </svg>
1666
- <script>
1667
- <segment name="Dsdyn" classid="CoreSegment"><![CDATA[#STORAGE REAL:8
1668
- ! v = R(w)*i + L0*di/dt ; R(w)=RMIN+K*w^2 ; dw/dt=ALPHA*v*i-BETA*w
1669
- ! CCBR/GEQ branch interface (seriesrl2 official pattern):
1670
- ! GEQ fixed G0 at TIMEZERO (matrix static -> zero refactorization);
1671
- ! per-step CCBR = I_hist + (G-G0)*V (RHS only).
1672
- ! G=1/(R+2L0/DELT); Z1=R-2L0/DELT; I_hist seriesrl2 recurrence.
1673
- ! Storage: (1)w (2)v_prev (3)i_prev (4)i_hist (5)g (6)r (7)ccbr (8)spare
1674
- #LOCAL REAL W
1675
- #LOCAL REAL V
1676
- #LOCAL REAL I
1677
- #LOCAL REAL R
1678
- #LOCAL REAL G
1679
- #LOCAL REAL G0
1680
- #LOCAL REAL DTB
1681
- #LOCAL REAL Z1
1682
- #LOCAL REAL IH
1683
- #LOCAL INTEGER IVD1_1
1684
- #BEGIN
1685
- GEQ($BRR, $SS) = 1.0/(2.0*$RMIN + 2.0*$L0/DELT)
1686
- DEFRDBR($BRR, $SS) = .TRUE.
1687
- #ENDBEGIN
1688
- IF (TIMEZERO) THEN
1689
- STORF(NSTORF+1) = 0.0
1690
- STORF(NSTORF+2) = 0.0
1691
- STORF(NSTORF+3) = 0.0
1692
- STORF(NSTORF+4) = 0.0
1693
- STORF(NSTORF+5) = 1.0/$RMIN
1694
- STORF(NSTORF+6) = $RMIN
1695
- STORF(NSTORF+7) = 0.0
1696
- STORF(NSTORF+8) = 0.0
1697
- CCBR($BRR, $SS) = 0.0
1698
- $OUT = 0.0
1699
- ENDIF
1700
- W = STORF(NSTORF+1)
1701
- IH = STORF(NSTORF+4)
1702
- G = STORF(NSTORF+5)
1703
- V = VBRANCH($SS, $BRR)
1704
- I = G*V + IH
1705
- DTB = 0.5 * $BETA * DELT
1706
- W = (W*(1.0 - DTB) + $ALPHA*DELT*(V*I)) / (1.0 + DTB)
1707
- %s R = $RMIN + $K * W * W
1708
- G = 1.0/(R + 2.0*$L0/DELT)
1709
- Z1 = R - 2.0*$L0/DELT
1710
- IH = (1.0 - Z1*G)*G*V - Z1*G*IH
1711
- G0 = 1.0/(2.0*$RMIN + 2.0*$L0/DELT)
1712
- CCBR($BRR, $SS) = IH + (G - G0)*V
1713
- STORF(NSTORF+1) = W
1714
- STORF(NSTORF+2) = V
1715
- STORF(NSTORF+3) = I
1716
- STORF(NSTORF+4) = IH
1717
- STORF(NSTORF+5) = G
1718
- STORF(NSTORF+6) = R
1719
- STORF(NSTORF+7) = CCBR($BRR, $SS)
1720
- STORF(NSTORF+8) = 0.0
1721
- NSTORF = NSTORF + 8
1722
- $OUT = W]]></segment>
1723
- <segment name="Branch" classid="CoreSegment"><![CDATA[BRR = $NA $NB BREAKER 1.0E6]]></segment>
1724
- <segment name="FlyBy" classid="CoreSegment"><![CDATA[Norton element (TMMB)
1725
- :NA
1726
- Positive terminal
1727
- :NB
1728
- Negative terminal
1729
- :OUT
1730
- State variable w]]></segment>
1731
- </script>
1732
- </Definition>''' % (int(time.time() * 1000) % 10 ** 9, pic)
1733
-
1734
-
1735
- def create_norton_element(x: int, y: int,
1736
- rmin: float = 1.0, k: float = 1.0,
1737
- alpha: float = 1.0, beta: float = 1.0,
1738
- l0: float = 0.0,
1739
- picard: int = 0,
1740
- name: Optional[str] = None) -> Dict[str, Any]:
1741
- """在当前画布创建"纯 Fortran Norton 自定义元件"(CCBR/GEQ 零重构范式)。
1742
-
1743
- 适用于任意"非线性电阻/阻抗支路"建模需求(TMMB 热磁忆阻分支参数化:
1744
- v=R(w)·i+L0·di/dt,R(w)=RMIN+K·w²,dw/dt=α·v·i−β·w)。元件无矩阵分支
1745
- (BREAKER 分支 + GEQ 固定 G0 + 每拍 CCBR 右端项注入)——导纳矩阵逐拍
1746
- 恒不变,零重构;支持 L0≥0 完整伴生模型(历史电流 I_hist 经 seriesrl2
1747
- 官方递推注入)。生成代码经验证:接地/悬浮、L0=0/0.1 四案例三路对照
1748
- 全部吻合(见 knowledge/emtdc_custom_element_norton.md)。
1749
-
1750
- Args:
1751
- x, y: 放置锚点坐标(网格坐标,mhi 画布)。
1752
- rmin: 最小电阻 [ohm](R(w) 下限,默认 1.0)。
1753
- k: 忆阻系数 [ohm](默认 1.0)。
1754
- alpha: 热耦合系数 [1/MW/s](PSCAD 内部 kV·kA=MW 语义,默认 1.0)。
1755
- beta: 冷却速率 [1/s](默认 1.0)。
1756
- l0: 串联电感 [H](≥0;完整 Norton 伴生,默认 0)。
1757
- picard: DSDYN 内 Picard 迭代次数(0/2/3,闭合 W↔R 代数环;默认 0)。
1758
- name: 显式 Name 标签;None 时自动生成唯一名(后续寻址主键)。
1759
-
1760
- Returns:
1761
- success: data 含 ``name``(唯一 Name 标签)、``component``、
1762
- ``defn``('<工程名>:TMMB')、``ports``({端口名: (x,y)})、
1763
- ``position``、``orient``、``params``(实际写入参数)。
1764
- error: 创建/注入定义失败(含 traceback 摘要)。
1765
- """
1766
- try:
1767
- bad = _require_canvas()
1768
- if bad:
1769
- return bad
1770
- if _prj is None:
1771
- return _err('工程对象不可用,请先调用 connect_and_load_project()')
1772
- xml = _norton_definition_xml(rmin, k, alpha, beta, l0, picard)
1773
- try:
1774
- _prj.create_definition(xml)
1775
- except Exception as exc:
1776
- return _err('create_definition 失败(Norton 元件定义注入):%s'
1777
- % exc)
1778
- final_name = name if name else _gen_name(_NORTON_DEF_NAME)
1779
- comp = _main.create_component('%s:%s' % (_prj_name, _NORTON_DEF_NAME),
1780
- int(round(x)), int(round(y)), orient=0,
1781
- Name=final_name, RMIN=rmin, K=k,
1782
- ALPHA=alpha, BETA=beta, L0=l0)
1783
- ports: Dict[str, Tuple[int, int]] = {}
1784
- try:
1785
- ports = {kk: tuple(vv[:2]) for kk, vv in comp.ports().items()}
1786
- except Exception:
1787
- pass
1788
- params = {'RMIN': rmin, 'K': k, 'ALPHA': alpha, 'BETA': beta,
1789
- 'L0': l0, 'picard': picard}
1790
- return _ok({'name': final_name, 'component': comp,
1791
- 'defn': '%s:%s' % (_prj_name, _NORTON_DEF_NAME),
1792
- 'ports': ports, 'position': (int(round(x)), int(round(y))),
1793
- 'orient': 0, 'params': params})
1794
- except Exception as exc:
1795
- return _err(exc)