setupEM 0.5.0__tar.gz → 0.5.1__tar.gz
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.
- {setupem-0.5.0/src/setupEM.egg-info → setupem-0.5.1}/PKG-INFO +1 -1
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/__init__.py +1 -1
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/palace_results.py +136 -10
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/setup_common.py +36 -0
- {setupem-0.5.0 → setupem-0.5.1/src/setupEM.egg-info}/PKG-INFO +1 -1
- {setupem-0.5.0 → setupem-0.5.1}/LICENSE +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/README.md +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/README_pypi.md +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/pyproject.toml +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/setup.cfg +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/__main__.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/data/SG13G2_100um.xml +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/data/SG13G2_200um.xml +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/data/SG13G2_200um_with_parameters.xml +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/data/SG13G2_nosub.xml +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/momentum_import.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/result_viewer.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/setupEM.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/setupThermal.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/stackupEditor.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM/stackup_writer.py +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM.egg-info/SOURCES.txt +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM.egg-info/dependency_links.txt +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM.egg-info/entry_points.txt +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM.egg-info/requires.txt +0 -0
- {setupem-0.5.0 → setupem-0.5.1}/src/setupEM.egg-info/top_level.txt +0 -0
|
@@ -25,6 +25,8 @@ import os
|
|
|
25
25
|
import json
|
|
26
26
|
import csv
|
|
27
27
|
import re
|
|
28
|
+
import math
|
|
29
|
+
import cmath
|
|
28
30
|
|
|
29
31
|
_ITERATION_RE = re.compile(r'^iteration(\d+)$')
|
|
30
32
|
|
|
@@ -79,14 +81,121 @@ def _read_error_indicators(dir_path):
|
|
|
79
81
|
try:
|
|
80
82
|
return {
|
|
81
83
|
'norm': float(fields['Norm']),
|
|
82
|
-
'minimum': float(fields['Minimum']),
|
|
83
84
|
'maximum': float(fields['Maximum']),
|
|
84
|
-
'mean': float(fields['Mean']),
|
|
85
85
|
}
|
|
86
86
|
except (KeyError, ValueError):
|
|
87
87
|
return None
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def _parse_palace_port_s_csv(path):
|
|
91
|
+
"""Parse a Palace port-S.csv file into (freq_list, S_dB_list, S_arg_list,
|
|
92
|
+
num_ports), where S_dB_list/S_arg_list are one dict-per-frequency mapping
|
|
93
|
+
'i j' port-pair strings to their |S| (dB) / phase (deg) string values.
|
|
94
|
+
"""
|
|
95
|
+
freq = []
|
|
96
|
+
S_dB = []
|
|
97
|
+
S_arg = []
|
|
98
|
+
params = []
|
|
99
|
+
num_ports = 0
|
|
100
|
+
|
|
101
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
102
|
+
for line in f:
|
|
103
|
+
aline = line.rstrip()
|
|
104
|
+
aline = aline.replace(",", "")
|
|
105
|
+
aline = aline.replace("(dB)", "")
|
|
106
|
+
aline = aline.replace("(deg.)", "")
|
|
107
|
+
|
|
108
|
+
if 'Hz)' in aline:
|
|
109
|
+
# header line: items are like |S[1][1]| arg(S[1][1])
|
|
110
|
+
items = aline.split()
|
|
111
|
+
for item in items:
|
|
112
|
+
if '|' in item:
|
|
113
|
+
Sxx = item.replace('|S', '').replace('|', '')
|
|
114
|
+
Sxx = Sxx.replace('][', ' ').replace('[', '').replace(']', '')
|
|
115
|
+
params.append(Sxx)
|
|
116
|
+
a, b = (int(x) for x in Sxx.split())
|
|
117
|
+
num_ports = max(num_ports, a, b)
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
items = aline.split()
|
|
121
|
+
if not items:
|
|
122
|
+
continue
|
|
123
|
+
dB = {}
|
|
124
|
+
arg = {}
|
|
125
|
+
for param in params:
|
|
126
|
+
dB_index = 2 * params.index(param) + 1
|
|
127
|
+
arg_index = dB_index + 1
|
|
128
|
+
dB[param] = items[dB_index]
|
|
129
|
+
arg[param] = items[arg_index]
|
|
130
|
+
freq.append(items[0])
|
|
131
|
+
S_dB.append(dB)
|
|
132
|
+
S_arg.append(arg)
|
|
133
|
+
|
|
134
|
+
return freq, S_dB, S_arg, num_ports
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _read_port_s_data(dir_path):
|
|
138
|
+
"""Parse dir_path/port-S.csv (if present), or None if the file is
|
|
139
|
+
missing, unfinished, or malformed (e.g. a still-running pass).
|
|
140
|
+
"""
|
|
141
|
+
path = os.path.join(dir_path, 'port-S.csv')
|
|
142
|
+
if not os.path.isfile(path):
|
|
143
|
+
return None
|
|
144
|
+
try:
|
|
145
|
+
freq, S_dB, S_arg, num_ports = _parse_palace_port_s_csv(path)
|
|
146
|
+
except (OSError, ValueError, IndexError):
|
|
147
|
+
return None
|
|
148
|
+
if not freq:
|
|
149
|
+
return None
|
|
150
|
+
return freq, S_dB, S_arg, num_ports
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _max_delta_s(prev_data, curr_data):
|
|
154
|
+
"""Max |S_curr - S_prev| across all ports and frequencies common to both
|
|
155
|
+
passes, mirroring HFSS's per-pass Max Delta S. Returns None if either
|
|
156
|
+
pass has no port-S data, or the two use different port counts.
|
|
157
|
+
"""
|
|
158
|
+
if prev_data is None or curr_data is None:
|
|
159
|
+
return None
|
|
160
|
+
freq_p, dB_p, arg_p, ports_p = prev_data
|
|
161
|
+
freq_c, dB_c, arg_c, ports_c = curr_data
|
|
162
|
+
if ports_p != ports_c:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
max_delta = 0.0
|
|
166
|
+
found_any = False
|
|
167
|
+
for idx in range(min(len(freq_p), len(freq_c))):
|
|
168
|
+
for key in set(dB_p[idx]) & set(dB_c[idx]):
|
|
169
|
+
try:
|
|
170
|
+
Sp = cmath.rect(10 ** (float(dB_p[idx][key]) / 20.0), math.radians(float(arg_p[idx][key])))
|
|
171
|
+
Sc = cmath.rect(10 ** (float(dB_c[idx][key]) / 20.0), math.radians(float(arg_c[idx][key])))
|
|
172
|
+
except ValueError:
|
|
173
|
+
continue
|
|
174
|
+
found_any = True
|
|
175
|
+
max_delta = max(max_delta, abs(Sc - Sp))
|
|
176
|
+
return max_delta if found_any else None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _collect_amr_rows(output_dir, iteration_dirs):
|
|
180
|
+
"""One row per AMR iteration subfolder, plus the root output_dir as
|
|
181
|
+
'Final': (label, palace_json_dict_or_None, error_indicators_dict_or_None,
|
|
182
|
+
max_delta_s_vs_previous_row_or_None).
|
|
183
|
+
"""
|
|
184
|
+
dirs_in_order = list(iteration_dirs) + [output_dir]
|
|
185
|
+
labels = [os.path.basename(d) for d in iteration_dirs] + ["Final"]
|
|
186
|
+
|
|
187
|
+
rows = []
|
|
188
|
+
prev_port_s = None
|
|
189
|
+
for label, d in zip(labels, dirs_in_order):
|
|
190
|
+
summary = _read_palace_json(d)
|
|
191
|
+
errors = _read_error_indicators(d)
|
|
192
|
+
port_s = _read_port_s_data(d)
|
|
193
|
+
delta_s = _max_delta_s(prev_port_s, port_s)
|
|
194
|
+
rows.append((label, summary, errors, delta_s))
|
|
195
|
+
prev_port_s = port_s
|
|
196
|
+
return rows
|
|
197
|
+
|
|
198
|
+
|
|
90
199
|
def _list_iteration_dirs(output_dir):
|
|
91
200
|
if not os.path.isdir(output_dir):
|
|
92
201
|
return []
|
|
@@ -128,6 +237,22 @@ def _format_sci(x):
|
|
|
128
237
|
return 'n/a' if x is None else f"{x:.3e}"
|
|
129
238
|
|
|
130
239
|
|
|
240
|
+
def _format_delta(x):
|
|
241
|
+
return 'n/a' if x is None else f"{x:.4f}"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _save_convergence_summary(run_path, summary_text):
|
|
245
|
+
"""Save the AMR summary table alongside the run's config.json /
|
|
246
|
+
port_information.json, so it's available on disk without re-running the
|
|
247
|
+
simulation or digging through the setupEM log.
|
|
248
|
+
"""
|
|
249
|
+
try:
|
|
250
|
+
with open(os.path.join(run_path, "mesh_convergence_summary.txt"), "w", encoding="utf-8") as f:
|
|
251
|
+
f.write(summary_text + "\n")
|
|
252
|
+
except OSError:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
|
|
131
256
|
def build_results_summary(run_path, model_basename):
|
|
132
257
|
"""Return a formatted multi-line summary of Palace results found under run_path,
|
|
133
258
|
or an explanatory message if nothing is there yet.
|
|
@@ -155,19 +280,18 @@ def build_results_summary(run_path, model_basename):
|
|
|
155
280
|
if errors:
|
|
156
281
|
lines.append(
|
|
157
282
|
f"Error indicator : Norm={_format_sci(errors['norm'])} "
|
|
158
|
-
f"Max={_format_sci(errors['maximum'])}
|
|
283
|
+
f"Max={_format_sci(errors['maximum'])}"
|
|
159
284
|
)
|
|
160
285
|
lines.append("=" * 40)
|
|
161
286
|
return "\n".join(lines)
|
|
162
287
|
|
|
163
288
|
# Adaptive mesh refinement: one row per iteration subfolder, plus the root as "Final"
|
|
164
|
-
rows =
|
|
165
|
-
for it_dir in iteration_dirs]
|
|
166
|
-
rows.append(("Final", _read_palace_json(output_dir), _read_error_indicators(output_dir)))
|
|
289
|
+
rows = _collect_amr_rows(output_dir, iteration_dirs)
|
|
167
290
|
|
|
168
|
-
headers = ["Iteration", "DOF", "Mesh elems", "Error Norm", "Error Max",
|
|
291
|
+
headers = ["Iteration", "DOF", "Mesh elems", "Error Norm", "Error Max",
|
|
292
|
+
"Max dS", "Time", "Peak RAM"]
|
|
169
293
|
table_rows = []
|
|
170
|
-
for label, summary, errors in rows:
|
|
294
|
+
for label, summary, errors, delta_s in rows:
|
|
171
295
|
summary = summary or {}
|
|
172
296
|
errors = errors or {}
|
|
173
297
|
table_rows.append([
|
|
@@ -176,7 +300,7 @@ def build_results_summary(run_path, model_basename):
|
|
|
176
300
|
_format_int(summary.get('mesh_elements')),
|
|
177
301
|
_format_sci(errors.get('norm')),
|
|
178
302
|
_format_sci(errors.get('maximum')),
|
|
179
|
-
|
|
303
|
+
_format_delta(delta_s),
|
|
180
304
|
_format_duration(summary.get('duration_s')),
|
|
181
305
|
_format_ram(summary.get('peak_ram_mb')),
|
|
182
306
|
])
|
|
@@ -196,4 +320,6 @@ def build_results_summary(run_path, model_basename):
|
|
|
196
320
|
for row in table_rows:
|
|
197
321
|
lines.append(fmt_row(row))
|
|
198
322
|
lines.append("=" * len(header_line))
|
|
199
|
-
|
|
323
|
+
summary_text = "\n".join(lines)
|
|
324
|
+
_save_convergence_summary(run_path, summary_text)
|
|
325
|
+
return summary_text
|
|
@@ -1411,6 +1411,42 @@ class MainWindowBase(QMainWindow):
|
|
|
1411
1411
|
|
|
1412
1412
|
TAB_HEADER_COLORS = ["#FFCDD2", "#C8E6C9", "#BBDEFB", "#FFF9C4", "#D1C4E9"]
|
|
1413
1413
|
|
|
1414
|
+
def __init__(self):
|
|
1415
|
+
super().__init__()
|
|
1416
|
+
# let the whole window accept a dropped *.simcfg / *.tsimcfg file,
|
|
1417
|
+
# not just the individual file-path fields (see FileDropLineEdit)
|
|
1418
|
+
self.setAcceptDrops(True)
|
|
1419
|
+
|
|
1420
|
+
# ---------- Drag & drop native config file onto the window ----------
|
|
1421
|
+
def _config_file_from_drop(self, event):
|
|
1422
|
+
if not event.mimeData().hasUrls():
|
|
1423
|
+
return None
|
|
1424
|
+
for url in event.mimeData().urls():
|
|
1425
|
+
path = url.toLocalFile()
|
|
1426
|
+
if path and pathlib.Path(path).suffix.upper() == "." + self.CONFIG_SUFFIX.upper():
|
|
1427
|
+
return path
|
|
1428
|
+
return None
|
|
1429
|
+
|
|
1430
|
+
def dragEnterEvent(self, event):
|
|
1431
|
+
if self._config_file_from_drop(event):
|
|
1432
|
+
event.acceptProposedAction()
|
|
1433
|
+
else:
|
|
1434
|
+
event.ignore()
|
|
1435
|
+
|
|
1436
|
+
def dragMoveEvent(self, event):
|
|
1437
|
+
if self._config_file_from_drop(event):
|
|
1438
|
+
event.acceptProposedAction()
|
|
1439
|
+
else:
|
|
1440
|
+
event.ignore()
|
|
1441
|
+
|
|
1442
|
+
def dropEvent(self, event):
|
|
1443
|
+
file_path = self._config_file_from_drop(event)
|
|
1444
|
+
if file_path:
|
|
1445
|
+
event.acceptProposedAction()
|
|
1446
|
+
self.load_configuration_from_file(file_path)
|
|
1447
|
+
else:
|
|
1448
|
+
event.ignore()
|
|
1449
|
+
|
|
1414
1450
|
# ---------- Menu Bar ----------
|
|
1415
1451
|
def create_menu_bar(self):
|
|
1416
1452
|
menu_bar = self.menuBar()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|