setupEM 0.6.0__tar.gz → 0.6.4__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.6.0 → setupem-0.6.4}/PKG-INFO +17 -3
- {setupem-0.6.0 → setupem-0.6.4}/README.md +10 -0
- setupem-0.6.0/src/setupEM.egg-info/PKG-INFO → setupem-0.6.4/README_pypi.md +13 -20
- {setupem-0.6.0 → setupem-0.6.4}/pyproject.toml +4 -2
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/__init__.py +1 -1
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/palace_results.py +23 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setupEM.py +187 -38
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setupThermal.py +87 -3
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/setup_common.py +665 -296
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/stackupEditor.py +112 -14
- setupem-0.6.4/src/setupEM/thermal_results.py +191 -0
- setupem-0.6.0/README_pypi.md → setupem-0.6.4/src/setupEM.egg-info/PKG-INFO +34 -1
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/SOURCES.txt +1 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/requires.txt +3 -1
- {setupem-0.6.0 → setupem-0.6.4}/LICENSE +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/setup.cfg +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/__main__.py +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_100um.xml +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_200um.xml +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_200um_with_parameters.xml +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/data/SG13G2_nosub.xml +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/momentum_import.py +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/result_viewer.py +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM/stackup_writer.py +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/dependency_links.txt +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/entry_points.txt +0 -0
- {setupem-0.6.0 → setupem-0.6.4}/src/setupEM.egg-info/top_level.txt +0 -0
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: setupEM
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
4
4
|
Summary: Python tool for configuration of gds2palace workflow with GUI.
|
|
5
5
|
Author-email: Volker Muehlhaus <volker@muehlhaus.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: gds2palace>=0.4.
|
|
10
|
+
Requires-Dist: gds2palace>=0.4.3
|
|
11
11
|
Requires-Dist: PySide6
|
|
12
|
+
Requires-Dist: shiboken6
|
|
12
13
|
Requires-Dist: scipy
|
|
13
14
|
Requires-Dist: requests
|
|
14
15
|
Requires-Dist: scikit-rf
|
|
15
16
|
Requires-Dist: matplotlib
|
|
16
17
|
Requires-Dist: numpy
|
|
17
18
|
Requires-Dist: gdspy
|
|
19
|
+
Requires-Dist: meshio
|
|
18
20
|
Dynamic: license-file
|
|
19
21
|
|
|
20
22
|
# Python GUI for gds2palace
|
|
@@ -27,6 +29,7 @@ setupEM includes two built-in tools for working with simulation results directly
|
|
|
27
29
|
- **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
|
|
28
30
|
|
|
29
31
|
- A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
|
|
32
|
+
- The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
|
|
30
33
|
- Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
|
|
31
34
|
- **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
|
|
32
35
|
|
|
@@ -42,6 +45,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
|
|
|
42
45
|
|
|
43
46
|
An overview of the SetupEM user interface is given below in chapter "Using setupEM"
|
|
44
47
|
|
|
48
|
+
Two more external tools are used by parts of the workflow, and are not installed automatically:
|
|
49
|
+
|
|
50
|
+
- [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
|
|
51
|
+
- An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
|
|
52
|
+
|
|
45
53
|
|
|
46
54
|
## Installing the AWS Palace FEM solver engine
|
|
47
55
|
**setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
|
|
@@ -101,10 +109,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
|
|
|
101
109
|
The setupEM module also installs these dependencies:
|
|
102
110
|
- gds2palace
|
|
103
111
|
- PySide6
|
|
112
|
+
- shiboken6
|
|
104
113
|
- scipy
|
|
105
114
|
- requests
|
|
106
115
|
- scikit-rf
|
|
107
116
|
- matplotlib
|
|
117
|
+
- numpy
|
|
118
|
+
- gdspy
|
|
119
|
+
- meshio
|
|
108
120
|
|
|
109
121
|
---
|
|
110
122
|
|
|
@@ -321,13 +333,15 @@ This project is licensed under the GNU General Public License v3.0 or later (GPL
|
|
|
321
333
|
|
|
322
334
|
**Note:** the `setupEM` PyPI package itself requires:
|
|
323
335
|
|
|
324
|
-
- gds2palace>=0.4.
|
|
336
|
+
- gds2palace>=0.4.3
|
|
325
337
|
- PySide6
|
|
338
|
+
- shiboken6
|
|
326
339
|
- scipy
|
|
327
340
|
- requests
|
|
328
341
|
- scikit-rf
|
|
329
342
|
- matplotlib
|
|
330
343
|
- numpy
|
|
331
344
|
- gdspy
|
|
345
|
+
- meshio
|
|
332
346
|
|
|
333
347
|
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -8,6 +8,7 @@ setupEM includes two built-in tools for working with simulation results directly
|
|
|
8
8
|
- **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
|
|
9
9
|
|
|
10
10
|
- A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
|
|
11
|
+
- The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
|
|
11
12
|
- Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
|
|
12
13
|
- **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
|
|
13
14
|
|
|
@@ -23,6 +24,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
|
|
|
23
24
|
|
|
24
25
|
An overview of the SetupEM user interface is given below in chapter "Using setupEM"
|
|
25
26
|
|
|
27
|
+
Two more external tools are used by parts of the workflow, and are not installed automatically:
|
|
28
|
+
|
|
29
|
+
- [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
|
|
30
|
+
- An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
|
|
31
|
+
|
|
26
32
|
|
|
27
33
|
## Installing the AWS Palace FEM solver engine
|
|
28
34
|
**setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
|
|
@@ -82,10 +88,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
|
|
|
82
88
|
The setupEM module also installs these dependencies:
|
|
83
89
|
- gds2palace
|
|
84
90
|
- PySide6
|
|
91
|
+
- shiboken6
|
|
85
92
|
- scipy
|
|
86
93
|
- requests
|
|
87
94
|
- scikit-rf
|
|
88
95
|
- matplotlib
|
|
96
|
+
- numpy
|
|
97
|
+
- gdspy
|
|
98
|
+
- meshio
|
|
89
99
|
|
|
90
100
|
---
|
|
91
101
|
|
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: setupEM
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: Python tool for configuration of gds2palace workflow with GUI.
|
|
5
|
-
Author-email: Volker Muehlhaus <volker@muehlhaus.com>
|
|
6
|
-
License-Expression: GPL-3.0-or-later
|
|
7
|
-
Requires-Python: >=3.9
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Requires-Dist: gds2palace>=0.4.0
|
|
11
|
-
Requires-Dist: PySide6
|
|
12
|
-
Requires-Dist: scipy
|
|
13
|
-
Requires-Dist: requests
|
|
14
|
-
Requires-Dist: scikit-rf
|
|
15
|
-
Requires-Dist: matplotlib
|
|
16
|
-
Requires-Dist: numpy
|
|
17
|
-
Requires-Dist: gdspy
|
|
18
|
-
Dynamic: license-file
|
|
19
|
-
|
|
20
1
|
# Python GUI for gds2palace
|
|
21
2
|
|
|
22
3
|
## What's New
|
|
@@ -27,6 +8,7 @@ setupEM includes two built-in tools for working with simulation results directly
|
|
|
27
8
|
- **Model Fit** (Create Model tab > Model Fit...) launches [snp2le](https://github.com/iic-jku/snp2le), an external open-source tool that extracts a lumped-element SPICE/Spectre netlist from S-parameter results - offering to install it via pip if it isn't already present. See section "[Model Fit](#model-fit)".
|
|
28
9
|
|
|
29
10
|
- A graphical **Stackup XML Editor** (Tools > Edit Stackup XML...), including Variables, Reference-relative positioning, Derived Layers, and Thermal Tables
|
|
11
|
+
- The stackup preview graphics are **interactive**: click a shape for its properties, and selection syncs both ways with the Stackup Editor's tables
|
|
30
12
|
- Input Files tab can now **override stackup Variables** (e.g. `total_thickness`) directly, without hand-editing the XML or the generated model
|
|
31
13
|
- **setupThermal**, a companion app for Elmer thermal simulation, alongside setupEM
|
|
32
14
|
|
|
@@ -42,6 +24,11 @@ The setupEM package now includes setupThermal also, which is the equivalent of s
|
|
|
42
24
|
|
|
43
25
|
An overview of the SetupEM user interface is given below in chapter "Using setupEM"
|
|
44
26
|
|
|
27
|
+
Two more external tools are used by parts of the workflow, and are not installed automatically:
|
|
28
|
+
|
|
29
|
+
- [ParaView](https://www.paraview.org/) — to view field-dump output (Palace/Elmer EM) and Elmer thermal result files via the "View fields/results in Paraview" buttons.
|
|
30
|
+
- An MPI implementation — only needed for multi-process Elmer runs (the Elmer solver settings' multithreading option). Use OpenMPI or MPICH on Linux/macOS; on Windows, install [Microsoft MPI](https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi) (setupEM checks for this and shows a download link if it's missing).
|
|
31
|
+
|
|
45
32
|
|
|
46
33
|
## Installing the AWS Palace FEM solver engine
|
|
47
34
|
**setupEM** creates and runs simulation models for the AWS Palace FEM solver engine. The underlying solver **AWS Palace** can be installed in multiple ways. For a smooth interaction with the gds2palace workflow, it is recommended to create some scripts that help running the model and convert the Palace results to SnP Touchstone files.
|
|
@@ -101,10 +88,14 @@ sudo apt install libxcb-cursor0 libxcb-xinerama0 libxcb-xkb1 libxcb-icccm4 libxc
|
|
|
101
88
|
The setupEM module also installs these dependencies:
|
|
102
89
|
- gds2palace
|
|
103
90
|
- PySide6
|
|
91
|
+
- shiboken6
|
|
104
92
|
- scipy
|
|
105
93
|
- requests
|
|
106
94
|
- scikit-rf
|
|
107
95
|
- matplotlib
|
|
96
|
+
- numpy
|
|
97
|
+
- gdspy
|
|
98
|
+
- meshio
|
|
108
99
|
|
|
109
100
|
---
|
|
110
101
|
|
|
@@ -321,13 +312,15 @@ This project is licensed under the GNU General Public License v3.0 or later (GPL
|
|
|
321
312
|
|
|
322
313
|
**Note:** the `setupEM` PyPI package itself requires:
|
|
323
314
|
|
|
324
|
-
- gds2palace>=0.4.
|
|
315
|
+
- gds2palace>=0.4.3
|
|
325
316
|
- PySide6
|
|
317
|
+
- shiboken6
|
|
326
318
|
- scipy
|
|
327
319
|
- requests
|
|
328
320
|
- scikit-rf
|
|
329
321
|
- matplotlib
|
|
330
322
|
- numpy
|
|
331
323
|
- gdspy
|
|
324
|
+
- meshio
|
|
332
325
|
|
|
333
326
|
(Other Python modules mentioned above are only needed to run standalone helper scripts in this repository, not to use the installed package.)
|
|
@@ -12,14 +12,16 @@ authors = [
|
|
|
12
12
|
{ name = "Volker Muehlhaus", email = "volker@muehlhaus.com" }
|
|
13
13
|
]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"gds2palace>=0.4.
|
|
15
|
+
"gds2palace>=0.4.3",
|
|
16
16
|
"PySide6",
|
|
17
|
+
"shiboken6",
|
|
17
18
|
"scipy",
|
|
18
19
|
"requests",
|
|
19
20
|
"scikit-rf",
|
|
20
21
|
"matplotlib",
|
|
21
22
|
"numpy",
|
|
22
|
-
"gdspy"
|
|
23
|
+
"gdspy",
|
|
24
|
+
"meshio"
|
|
23
25
|
]
|
|
24
26
|
requires-python = ">=3.9"
|
|
25
27
|
|
|
@@ -27,6 +27,7 @@ import csv
|
|
|
27
27
|
import re
|
|
28
28
|
import math
|
|
29
29
|
import cmath
|
|
30
|
+
import glob
|
|
30
31
|
|
|
31
32
|
_ITERATION_RE = re.compile(r'^iteration(\d+)$')
|
|
32
33
|
|
|
@@ -47,6 +48,28 @@ def find_output_dir(run_path, model_basename):
|
|
|
47
48
|
return os.path.normpath(os.path.join(run_path, output_rel))
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
def find_paraview_files(run_path, model_basename):
|
|
52
|
+
"""All ParaView collection files under this run's Palace output directory, found
|
|
53
|
+
recursively. Palace's own field-dump feature (triggered by fdump/SaveStep) writes
|
|
54
|
+
its category/excitation folder structure without gds2palace's control - confirmed
|
|
55
|
+
(on two Palace versions) to only keep the last-solved excitation's Cycle files, so
|
|
56
|
+
no fixed filename or count can be assumed here. Prefers .pvd, then falls back to
|
|
57
|
+
.pvtu (the multi-partition index - ties per-partition pieces back into one dataset),
|
|
58
|
+
then loose .vtu if neither exists yet. Empty list if the output directory doesn't
|
|
59
|
+
exist / has nothing yet.
|
|
60
|
+
"""
|
|
61
|
+
output_dir = find_output_dir(run_path, model_basename)
|
|
62
|
+
if not os.path.isdir(output_dir):
|
|
63
|
+
return []
|
|
64
|
+
pvd_files = sorted(glob.glob(os.path.join(output_dir, '**', '*.pvd'), recursive=True))
|
|
65
|
+
if pvd_files:
|
|
66
|
+
return pvd_files
|
|
67
|
+
pvtu_files = sorted(glob.glob(os.path.join(output_dir, '**', '*.pvtu'), recursive=True))
|
|
68
|
+
if pvtu_files:
|
|
69
|
+
return pvtu_files
|
|
70
|
+
return sorted(glob.glob(os.path.join(output_dir, '**', '*.vtu'), recursive=True))
|
|
71
|
+
|
|
72
|
+
|
|
50
73
|
def _read_palace_json(dir_path):
|
|
51
74
|
path = os.path.join(dir_path, 'palace.json')
|
|
52
75
|
if not os.path.isfile(path):
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
########################################################################
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
import sys, json, os, pathlib, ast, webbrowser, argparse, shutil, re
|
|
20
|
+
import sys, json, os, pathlib, ast, webbrowser, argparse, shutil, re, glob
|
|
21
21
|
import numpy as np
|
|
22
22
|
import importlib.metadata
|
|
23
23
|
import importlib.util
|
|
@@ -55,7 +55,7 @@ if __package__ in (None, ""):
|
|
|
55
55
|
VectorWidget, PopUpWindow, CreateModelTabBase, MainWindowBase,
|
|
56
56
|
epsilon_to_color, default_stackup_dielectric_label, default_stackup_metal_label,
|
|
57
57
|
)
|
|
58
|
-
from palace_results import build_results_summary, find_output_dir
|
|
58
|
+
from palace_results import build_results_summary, find_output_dir, find_paraview_files
|
|
59
59
|
else:
|
|
60
60
|
from .setup_common import (
|
|
61
61
|
EDIT_STYLE_OPTIONAL, EDIT_STYLE_REQUIRED, COMBO_STYLE_REQUIRED, COMBO_STYLE_OPTIONAL,
|
|
@@ -63,7 +63,7 @@ else:
|
|
|
63
63
|
VectorWidget, PopUpWindow, CreateModelTabBase, MainWindowBase,
|
|
64
64
|
epsilon_to_color, default_stackup_dielectric_label, default_stackup_metal_label,
|
|
65
65
|
)
|
|
66
|
-
from .palace_results import build_results_summary, find_output_dir
|
|
66
|
+
from .palace_results import build_results_summary, find_output_dir, find_paraview_files
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
'''
|
|
@@ -165,10 +165,21 @@ class FrequenciesTab(QWidget):
|
|
|
165
165
|
self.dump_layout = QHBoxLayout()
|
|
166
166
|
|
|
167
167
|
self.fdump_layout = QVBoxLayout()
|
|
168
|
-
self.
|
|
168
|
+
self.fdump_label = QLabel("fdump [GHz], values separated by comma")
|
|
169
|
+
self.fdump_layout.addWidget(self.fdump_label)
|
|
169
170
|
self.fdump_edit = QLineEdit("")
|
|
170
171
|
self.fdump_edit.setStyleSheet(EDIT_STYLE_OPTIONAL)
|
|
171
172
|
self.fdump_layout.addWidget(self.fdump_edit)
|
|
173
|
+
|
|
174
|
+
# Elmer has no per-sample SaveStep like Palace - any fdump value turns on
|
|
175
|
+
# field-dump output at every solved frequency (sweep + fpoint together), so a
|
|
176
|
+
# per-frequency list is misleading there. Elmer mode shows this checkbox instead
|
|
177
|
+
# of fdump_edit; only one of the two is ever visible, toggled in setPalaceMode()/
|
|
178
|
+
# setElmerMode(). Both widgets always exist so mode switches are just a visibility
|
|
179
|
+
# flip, no rebuilding.
|
|
180
|
+
self.fdump_enabled_checkbox = QCheckBox("Enable field dump at all frequencies")
|
|
181
|
+
self.fdump_layout.addWidget(self.fdump_enabled_checkbox)
|
|
182
|
+
|
|
172
183
|
self.dump_layout.addLayout(self.fdump_layout)
|
|
173
184
|
self.dump_group.setLayout(self.dump_layout)
|
|
174
185
|
|
|
@@ -184,7 +195,9 @@ class FrequenciesTab(QWidget):
|
|
|
184
195
|
def save_values(self):
|
|
185
196
|
# fstart/fstop may be left empty only if fpoint or fdump supplies at least
|
|
186
197
|
# one frequency instead (gds2palace treats fstart/fstop as fully optional)
|
|
187
|
-
|
|
198
|
+
fdump_given = (self.fdump_enabled_checkbox.isChecked() if self.MainWindow.ElmerMode
|
|
199
|
+
else self.fdump_edit.text() != "")
|
|
200
|
+
discrete_freqs_given = (self.fpoint_edit.text() != "" or fdump_given)
|
|
188
201
|
|
|
189
202
|
fstart = None
|
|
190
203
|
if self.start_edit.text() == "":
|
|
@@ -239,16 +252,35 @@ class FrequenciesTab(QWidget):
|
|
|
239
252
|
else:
|
|
240
253
|
saved_values.pop("fpoint",None) # delete key
|
|
241
254
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
saved_values
|
|
255
|
+
if self.MainWindow.ElmerMode:
|
|
256
|
+
saved_values["fdump_enabled"] = self.fdump_enabled_checkbox.isChecked()
|
|
257
|
+
# don't touch saved_values["fdump"] here - it's Palace-only data (a real
|
|
258
|
+
# frequency list) and must survive a round-trip through Elmer mode untouched
|
|
259
|
+
have_sweep = ("fstart" in saved_values and "fstop" in saved_values)
|
|
260
|
+
have_fpoint = bool(saved_values.get("fpoint"))
|
|
261
|
+
if self.fdump_enabled_checkbox.isChecked() and not have_sweep and not have_fpoint:
|
|
262
|
+
QMessageBox.warning(self, "Error",
|
|
263
|
+
"Field dump needs at least one frequency: set fstart/fstop or fpoint first.")
|
|
264
|
+
self.fdump_enabled_checkbox.setChecked(False)
|
|
265
|
+
saved_values["fdump_enabled"] = False
|
|
266
|
+
return False
|
|
245
267
|
else:
|
|
246
|
-
|
|
268
|
+
text = self.fdump_edit.text()
|
|
269
|
+
if text != "":
|
|
270
|
+
saved_values ["fdump"] = ast.literal_eval('['+text+']')
|
|
271
|
+
else:
|
|
272
|
+
saved_values.pop("fdump",None)
|
|
273
|
+
# don't touch saved_values["fdump_enabled"] here either, same reasoning
|
|
274
|
+
# "View fields in Paraview" is only relevant once fdump is set
|
|
275
|
+
self.MainWindow.create_model_tab._update_paraview_button_visibility()
|
|
247
276
|
|
|
248
277
|
# if fstart == fstop == fdump or fstart == fstop == fstep, then remove fstart, fstop
|
|
249
278
|
if fstart is not None and fstop is not None and fstart == fstop:
|
|
250
279
|
discrete_list1 = saved_values.get("fpoint", [])
|
|
251
|
-
|
|
280
|
+
# fdump no longer holds real per-frequency data in Elmer mode, and collapsing
|
|
281
|
+
# the sweep away here would remove the very fstop value the field-dump
|
|
282
|
+
# checkbox's generated code depends on (see ModelEditorTab.create_model_text)
|
|
283
|
+
discrete_list2 = [] if self.MainWindow.ElmerMode else saved_values.get("fdump", [])
|
|
252
284
|
if fstart in discrete_list1 or fstart in discrete_list2:
|
|
253
285
|
saved_values.pop("fstart", None)
|
|
254
286
|
saved_values.pop("fstop", None)
|
|
@@ -263,7 +295,9 @@ class FrequenciesTab(QWidget):
|
|
|
263
295
|
def load_values(self):
|
|
264
296
|
# if fstart/fstop were intentionally omitted in favor of fpoint/fdump, keep the
|
|
265
297
|
# fields blank on reload instead of repopulating the "0"/"50" sweep defaults
|
|
266
|
-
|
|
298
|
+
fdump_given = saved_values.get("fdump_enabled", False) if self.MainWindow.ElmerMode \
|
|
299
|
+
else ("fdump" in saved_values)
|
|
300
|
+
discrete_freqs_given = ("fpoint" in saved_values) or fdump_given
|
|
267
301
|
fstart_default = "" if discrete_freqs_given else "0"
|
|
268
302
|
fstop_default = "" if discrete_freqs_given else "50"
|
|
269
303
|
self.start_edit.setText(str(saved_values.get("fstart",fstart_default)))
|
|
@@ -275,6 +309,7 @@ class FrequenciesTab(QWidget):
|
|
|
275
309
|
|
|
276
310
|
float_list = saved_values.get("fdump","")
|
|
277
311
|
self.fdump_edit.setText(','.join(map(str, float_list)))
|
|
312
|
+
self.fdump_enabled_checkbox.setChecked(bool(saved_values.get("fdump_enabled", False)))
|
|
278
313
|
|
|
279
314
|
|
|
280
315
|
class PortsTab(QWidget):
|
|
@@ -1285,13 +1320,26 @@ class CreateModelTab(CreateModelTabBase):
|
|
|
1285
1320
|
# up exactly with Preview/Create Mesh/Start Simulation above, in the same
|
|
1286
1321
|
# Actions group.
|
|
1287
1322
|
row = self.buttons_grid.rowCount()
|
|
1288
|
-
self.view_results_btn = QPushButton("📈 View
|
|
1323
|
+
self.view_results_btn = QPushButton("📈 View S-Parameters...")
|
|
1289
1324
|
self.view_results_btn.clicked.connect(self.MainWindow.open_result_viewer)
|
|
1290
1325
|
self.buttons_grid.addWidget(self.view_results_btn, row, 0)
|
|
1291
1326
|
self.model_fit_btn = QPushButton("🧩 Model Fit...")
|
|
1292
1327
|
self.model_fit_btn.clicked.connect(self.open_model_fit)
|
|
1293
1328
|
self.buttons_grid.addWidget(self.model_fit_btn, row, 1)
|
|
1294
1329
|
|
|
1330
|
+
# "View fields in Paraview" opens field-dump data (Palace fdump / Elmer EM
|
|
1331
|
+
# fields*.vtu), a separate row since it's independent of the S-parameter
|
|
1332
|
+
# viewer/model fit above. Only meaningful when fdump is set (otherwise there's
|
|
1333
|
+
# never any field data to open), so it's hidden rather than shown greyed-out -
|
|
1334
|
+
# kept in sync with saved_values['fdump'] via _update_paraview_button_visibility(),
|
|
1335
|
+
# called from here, from load_values() (project/model import), and from
|
|
1336
|
+
# FrequenciesTab.save_values() (live edits to the fdump field).
|
|
1337
|
+
row = self.buttons_grid.rowCount()
|
|
1338
|
+
self.paraview_btn = QPushButton("🖼️ View fields in Paraview...")
|
|
1339
|
+
self.paraview_btn.clicked.connect(self.launch_paraview)
|
|
1340
|
+
self.buttons_grid.addWidget(self.paraview_btn, row, 0)
|
|
1341
|
+
self._update_paraview_button_visibility()
|
|
1342
|
+
|
|
1295
1343
|
def open_model_fit(self):
|
|
1296
1344
|
SNP2LE_URL = "https://github.com/iic-jku/snp2le"
|
|
1297
1345
|
|
|
@@ -1370,6 +1418,47 @@ class CreateModelTab(CreateModelTabBase):
|
|
|
1370
1418
|
summary = build_results_summary(run_path, saved_values['model_basename'])
|
|
1371
1419
|
self.log_area.appendPlainText("\n" + summary + "\n")
|
|
1372
1420
|
|
|
1421
|
+
def _update_paraview_button_visibility(self):
|
|
1422
|
+
if self.MainWindow.ElmerMode:
|
|
1423
|
+
visible = bool(saved_values.get('fdump_enabled'))
|
|
1424
|
+
else:
|
|
1425
|
+
visible = bool(saved_values.get('fdump'))
|
|
1426
|
+
self.paraview_btn.setVisible(visible)
|
|
1427
|
+
|
|
1428
|
+
def load_values(self):
|
|
1429
|
+
super().load_values()
|
|
1430
|
+
self._update_paraview_button_visibility()
|
|
1431
|
+
|
|
1432
|
+
def launch_paraview(self):
|
|
1433
|
+
if self.MainWindow.PalaceMode:
|
|
1434
|
+
run_path = saved_values['sim_path'] + "/palace_model/" + saved_values['model_basename'] + "_data"
|
|
1435
|
+
file_paths = find_paraview_files(run_path, saved_values['model_basename'])
|
|
1436
|
+
not_found = (
|
|
1437
|
+
f"⚠️ No Palace field-dump output found under "
|
|
1438
|
+
f"{find_output_dir(run_path, saved_values['model_basename'])}\n"
|
|
1439
|
+
"(set fdump to specific frequencies before running the simulation)\n"
|
|
1440
|
+
)
|
|
1441
|
+
else:
|
|
1442
|
+
run_path = saved_values['sim_path'] + "/elmer_model/" + saved_values['model_basename'] + "_data"
|
|
1443
|
+
# Output File Name = File "fields" has no path prefix, so Elmer resolves it
|
|
1444
|
+
# relative to the Mesh DB directory ("mesh" under run_path) rather than
|
|
1445
|
+
# run_path itself - confirmed against a real run (same resolution mechanism
|
|
1446
|
+
# found for thermal_results.vtu). Check run_path too, defensively.
|
|
1447
|
+
search_dirs = [os.path.join(run_path, "mesh"), run_path]
|
|
1448
|
+
file_paths = []
|
|
1449
|
+
for pattern in ("fields*.pvd", "fields*.pvtu", "fields*.vtu"):
|
|
1450
|
+
for d in search_dirs:
|
|
1451
|
+
file_paths = sorted(glob.glob(os.path.join(d, pattern)))
|
|
1452
|
+
if file_paths:
|
|
1453
|
+
break
|
|
1454
|
+
if file_paths:
|
|
1455
|
+
break
|
|
1456
|
+
not_found = (
|
|
1457
|
+
f"⚠️ No Elmer field-dump output found under {run_path}\n"
|
|
1458
|
+
"(enable field dump before running the simulation)\n"
|
|
1459
|
+
)
|
|
1460
|
+
self._open_in_paraview(file_paths, not_found)
|
|
1461
|
+
|
|
1373
1462
|
def on_finished(self, exit_code, exit_status):
|
|
1374
1463
|
super().on_finished(exit_code, exit_status)
|
|
1375
1464
|
# Auto-append the results summary after a real simulation run (not after mesh creation)
|
|
@@ -1431,9 +1520,13 @@ class CreateModelTab(CreateModelTabBase):
|
|
|
1431
1520
|
|
|
1432
1521
|
Palace writes all of its results into one dedicated subdirectory (named in
|
|
1433
1522
|
config.json's Problem.Output, resolved via find_output_dir()), so that whole
|
|
1434
|
-
subtree is the deletion target. Elmer
|
|
1435
|
-
|
|
1436
|
-
|
|
1523
|
+
subtree is the deletion target. Elmer's SaveScalars/ResultOutputSolver write
|
|
1524
|
+
bare filenames (no path prefix) in their .sif config, so Elmer resolves them
|
|
1525
|
+
relative to the Mesh DB directory ("mesh" under run_path) rather than run_path
|
|
1526
|
+
itself - confirmed against real runs (same resolution mechanism found for
|
|
1527
|
+
thermal_results.vtu, and combine_snp.py itself expects "scalar_results" under
|
|
1528
|
+
a "mesh" parent and writes the resulting .sNp file there too). run_path itself
|
|
1529
|
+
is also checked, defensively, in case some variant writes there directly.
|
|
1437
1530
|
"""
|
|
1438
1531
|
if self.MainWindow.PalaceMode:
|
|
1439
1532
|
output_dir = find_output_dir(run_path, saved_values['model_basename'])
|
|
@@ -1442,12 +1535,14 @@ class CreateModelTab(CreateModelTabBase):
|
|
|
1442
1535
|
targets = [output_dir]
|
|
1443
1536
|
else:
|
|
1444
1537
|
targets = []
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1538
|
+
for search_dir in (os.path.join(run_path, "mesh"), run_path):
|
|
1539
|
+
if not os.path.isdir(search_dir):
|
|
1540
|
+
continue
|
|
1541
|
+
for fn in os.listdir(search_dir):
|
|
1542
|
+
full_path = os.path.join(search_dir, fn)
|
|
1448
1543
|
if not os.path.isfile(full_path):
|
|
1449
1544
|
continue
|
|
1450
|
-
if fn in ("scalar_results
|
|
1545
|
+
if fn in ("scalar_results", "scalar_results.names") or \
|
|
1451
1546
|
fn.startswith("fields") or re.search(r'\.s\d+p$', fn, re.IGNORECASE):
|
|
1452
1547
|
targets.append(full_path)
|
|
1453
1548
|
if not targets:
|
|
@@ -1546,17 +1641,27 @@ class CreateModelTab(CreateModelTabBase):
|
|
|
1546
1641
|
|
|
1547
1642
|
self.log_area.appendPlainText('Setting work directory ' + run_path)
|
|
1548
1643
|
|
|
1549
|
-
#
|
|
1550
|
-
#
|
|
1551
|
-
#
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1644
|
+
# create_elmer_run_script() (util_utilities.py) writes run_elmer.bat
|
|
1645
|
+
# directly on Windows (proper .bat content - no "#!/bin/bash" shebang,
|
|
1646
|
+
# and MS-MPI's "mpiexec -n N" instead of the Linux-only "mpirun -np N"),
|
|
1647
|
+
# so no rename step is needed here any more.
|
|
1648
|
+
if saved_values.get('ELMER_MPI_THREADS', 1) > 1 and shutil.which("mpiexec") is None:
|
|
1649
|
+
self.log_area.appendPlainText(
|
|
1650
|
+
"⚠️ This model requests MPI multithreading, but 'mpiexec' was not "
|
|
1651
|
+
"found on PATH. On Windows, Elmer uses Microsoft MPI - download and "
|
|
1652
|
+
"install it from "
|
|
1653
|
+
"https://learn.microsoft.com/en-us/message-passing-interface/microsoft-mpi "
|
|
1654
|
+
"and restart setupEM, or switch to 1 thread (no multithreading) on the "
|
|
1655
|
+
"Mesh and Boundaries tab.\n"
|
|
1656
|
+
)
|
|
1657
|
+
return
|
|
1557
1658
|
self.process.setWorkingDirectory(run_path)
|
|
1558
|
-
# start simulation
|
|
1559
|
-
|
|
1659
|
+
# start simulation - full path, not just "run_elmer.bat": Windows'
|
|
1660
|
+
# CreateProcess resolves a bare relative program name against the
|
|
1661
|
+
# CALLING process's own cwd/PATH, not the child's setWorkingDirectory(),
|
|
1662
|
+
# so a bare filename here silently fails with FailedToStart even though
|
|
1663
|
+
# setWorkingDirectory() is set correctly.
|
|
1664
|
+
self.process.start(os.path.join(run_path, "run_elmer.bat"))
|
|
1560
1665
|
else:
|
|
1561
1666
|
# Linux
|
|
1562
1667
|
self.log_area.appendPlainText('Setting work directory ' + run_path)
|
|
@@ -1663,13 +1768,22 @@ class ModelEditorTab(QWidget):
|
|
|
1663
1768
|
special_keylist = ['simulation_ports','materials_list','dielectrics_list','metals_list',
|
|
1664
1769
|
'layernumbers','allpolygons']
|
|
1665
1770
|
# List of keys that we don't write to Python model code editor
|
|
1666
|
-
|
|
1771
|
+
# fdump_enabled is a GUI-only toggle (Elmer mode), never a real gds2palace setting
|
|
1772
|
+
ignore_list = ['model_basename','sim_path','fdump_enabled']
|
|
1667
1773
|
|
|
1668
1774
|
|
|
1669
1775
|
# Keywords that are excluded in Palace mode
|
|
1670
1776
|
if self.MainWindow.PalaceMode:
|
|
1671
1777
|
ignore_list.append('iterative')
|
|
1672
1778
|
|
|
1779
|
+
if self.MainWindow.ElmerMode:
|
|
1780
|
+
# Elmer mode shows a plain on/off checkbox (fdump_enabled) instead of Palace's
|
|
1781
|
+
# per-frequency fdump list - suppress the generic per-key emission of fdump
|
|
1782
|
+
# here so a stale Palace-mode list saved earlier in the same session doesn't
|
|
1783
|
+
# leak into an Elmer-mode script; the synthesized settings['fdump'] line
|
|
1784
|
+
# (below, after special_keylist) is emitted instead when the checkbox is on.
|
|
1785
|
+
ignore_list.append('fdump')
|
|
1786
|
+
|
|
1673
1787
|
if forExport:
|
|
1674
1788
|
# these commands are only used within this GUI application to control gmsh
|
|
1675
1789
|
ignore_list.extend(['preview_only','no_preview'])
|
|
@@ -1679,6 +1793,23 @@ class ModelEditorTab(QWidget):
|
|
|
1679
1793
|
if not key in ignore_list:
|
|
1680
1794
|
add_key(key)
|
|
1681
1795
|
|
|
1796
|
+
if self.MainWindow.ElmerMode and bool(saved_values.get('fdump_enabled')):
|
|
1797
|
+
# Elmer has no per-frequency SaveStep like Palace - it dumps fields at every
|
|
1798
|
+
# solved frequency once any fdump value is set, so reuse an already-solved
|
|
1799
|
+
# frequency here rather than asking the user to pick one. This is harmless
|
|
1800
|
+
# (adds no extra solve) as long as util_elmer.write_elmer_frequencies()
|
|
1801
|
+
# dedupes the combined frequency list, which it does.
|
|
1802
|
+
# Known limitation: this line's RHS references "settings", so
|
|
1803
|
+
# parse_assignments() (setup_common.py) skips it entirely on Import -
|
|
1804
|
+
# re-importing an exported Elmer+fdump script loses fdump_enabled (loads
|
|
1805
|
+
# unchecked). Not fixed here since it's a silent no-op, not a crash.
|
|
1806
|
+
add_text("\n# 'Enable field dump' checkbox: Elmer dumps fields at every solved")
|
|
1807
|
+
add_text("# frequency once fdump is non-empty, so reuse an already-solved one")
|
|
1808
|
+
if 'fstop' in saved_values:
|
|
1809
|
+
add_text("settings['fdump'] = [settings['fstop']]")
|
|
1810
|
+
elif saved_values.get('fpoint'):
|
|
1811
|
+
add_text("settings['fdump'] = [settings['fpoint'][0]]")
|
|
1812
|
+
|
|
1682
1813
|
|
|
1683
1814
|
add_text("\n# ===================== port definitions =======================")
|
|
1684
1815
|
add_text("simulation_ports = simulation_setup.all_simulation_ports()")
|
|
@@ -1883,6 +2014,10 @@ class MainWindow(MainWindowBase):
|
|
|
1883
2014
|
self.ElmerMode = False
|
|
1884
2015
|
self.setWindowTitle(APP_NAME + ' Palace')
|
|
1885
2016
|
self.frequencies_tab.dump_group.setVisible(True)
|
|
2017
|
+
self.frequencies_tab.dump_group.setTitle("Optional list of fixed frequencies creating field dump data for visualization (Paraview files))")
|
|
2018
|
+
self.frequencies_tab.fdump_label.setVisible(True)
|
|
2019
|
+
self.frequencies_tab.fdump_edit.setVisible(True)
|
|
2020
|
+
self.frequencies_tab.fdump_enabled_checkbox.setVisible(False)
|
|
1886
2021
|
self.mesh_tab.AMR_group.setVisible(True)
|
|
1887
2022
|
self.mesh_tab.Elmer_group.setVisible(False)
|
|
1888
2023
|
|
|
@@ -1895,7 +2030,14 @@ class MainWindow(MainWindowBase):
|
|
|
1895
2030
|
self.PalaceMode = False
|
|
1896
2031
|
self.ElmerMode = True
|
|
1897
2032
|
self.setWindowTitle(APP_NAME + ' Elmer')
|
|
1898
|
-
|
|
2033
|
+
# Elmer has no per-sample SaveStep like Palace - any fdump value turns on
|
|
2034
|
+
# field-dump output at every solved frequency, so it shows a plain on/off
|
|
2035
|
+
# checkbox here instead of Palace's per-frequency fdump list.
|
|
2036
|
+
self.frequencies_tab.dump_group.setVisible(True)
|
|
2037
|
+
self.frequencies_tab.dump_group.setTitle("Create field dump data for visualization (Paraview files)")
|
|
2038
|
+
self.frequencies_tab.fdump_label.setVisible(False)
|
|
2039
|
+
self.frequencies_tab.fdump_edit.setVisible(False)
|
|
2040
|
+
self.frequencies_tab.fdump_enabled_checkbox.setVisible(True)
|
|
1899
2041
|
self.mesh_tab.AMR_group.setVisible(False)
|
|
1900
2042
|
self.mesh_tab.Elmer_group.setVisible(True)
|
|
1901
2043
|
|
|
@@ -1925,8 +2067,8 @@ class MainWindow(MainWindowBase):
|
|
|
1925
2067
|
|
|
1926
2068
|
|
|
1927
2069
|
def show_version(self):
|
|
1928
|
-
setupEM_version =
|
|
1929
|
-
gds2palace_version =
|
|
2070
|
+
setupEM_version = self.get_setupEM_version()
|
|
2071
|
+
gds2palace_version = self.get_gds2palace_version()
|
|
1930
2072
|
# snp2le (used by Model Fit) is an optional dependency, not installed by default
|
|
1931
2073
|
try:
|
|
1932
2074
|
snp2le_version = importlib.metadata.version("snp2le")
|
|
@@ -1969,13 +2111,20 @@ class MainWindow(MainWindowBase):
|
|
|
1969
2111
|
ports = parse_python_ports_definitions(file_path)
|
|
1970
2112
|
self.ports_tab.update_port_from_import(ports)
|
|
1971
2113
|
|
|
1972
|
-
# Elmer/Palace mode
|
|
1973
|
-
#
|
|
1974
|
-
#
|
|
1975
|
-
#
|
|
2114
|
+
# Elmer/Palace mode isn't captured by the general settings-dict import
|
|
2115
|
+
# (parse_assignments() skips lines whose value contains "settings", and
|
|
2116
|
+
# 'elmer' isn't in import_mapping anyway), so detect it directly from the
|
|
2117
|
+
# source text instead. Two valid ways a script selects Elmer mode:
|
|
2118
|
+
# - calling simulation_setup.create_elmer(...) (what create_model_text()
|
|
2119
|
+
# itself generates, with a space before "(" - a plain "create_elmer("
|
|
2120
|
+
# substring check never matches that)
|
|
2121
|
+
# - setting settings['elmer'] = True directly and calling create_model()
|
|
2122
|
+
# itself (create_elmer() is only a thin wrapper that sets this same flag)
|
|
1976
2123
|
with open(file_path) as f:
|
|
1977
2124
|
text = f.read()
|
|
1978
|
-
|
|
2125
|
+
elmer_call = re.search(r'create_elmer\s*\(', text)
|
|
2126
|
+
elmer_flag = re.search(r'settings\s*\[\s*[\'"]elmer[\'"]\s*\]\s*=\s*True\b', text, re.IGNORECASE)
|
|
2127
|
+
if elmer_call or elmer_flag:
|
|
1979
2128
|
self.setElmerMode()
|
|
1980
2129
|
else:
|
|
1981
2130
|
self.setPalaceMode()
|