Skip to content

Commit 66716fc

Browse files
Devin-Crawfordpre-commit-ci[bot]SMoraisAnsysmaxcapodi78Samuelopez-ansys
authored
FIX: Enable field plotting with discrete sweep (#5683)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastien Morais <[email protected]> Co-authored-by: mcapodif <[email protected]> Co-authored-by: Samuel Lopez <[email protected]>
1 parent 29b7d2f commit 66716fc

File tree

10 files changed

+161
-51
lines changed

10 files changed

+161
-51
lines changed

src/ansys/aedt/core/hfss.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -814,8 +814,8 @@ def create_setup(self, name="MySetupAuto", setup_type=None, **kwargs):
814814
"""Create an analysis setup for HFSS.
815815
816816
Optional arguments are passed along with ``setup_type`` and ``name``. Keyword
817-
names correspond to the ``setup_type`` corresponding to the native AEDT API.
818-
The list of keywords here is not exhaustive.
817+
names correspond to keyword for the ``setup_type`` as defined in
818+
the native AEDT API.
819819
820820
.. note::
821821
This method overrides the ``Analysis.setup()`` method for the HFSS app.
@@ -860,9 +860,9 @@ def create_setup(self, name="MySetupAuto", setup_type=None, **kwargs):
860860
setup.auto_update = False
861861
for arg_name, arg_value in kwargs.items():
862862
if setup[arg_name] is not None:
863-
if arg_name == "MultipleAdaptiveFreqsSetup":
864-
setup[arg_name].delete_all()
865-
if isinstance(arg_value, list):
863+
if arg_name == "MultipleAdaptiveFreqsSetup": # A list of frequency values is passed if
864+
setup[arg_name].delete_all() # the default convergence criteria are to be
865+
if isinstance(arg_value, list): # used.
866866
for i in arg_value:
867867
setup[arg_name][i] = [0.02]
868868
else:

src/ansys/aedt/core/modules/setup_templates.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,19 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq):
16141614

16151615
TR = {}
16161616

1617+
# Default sweep settings for Q3D
1618+
SweepQ3D = dict(
1619+
{
1620+
"IsEnabled": True,
1621+
"RangeType": "LinearStep",
1622+
"RangeStart": "1GHz",
1623+
"RangeEnd": "10GHz",
1624+
"Type": "Discrete",
1625+
"SaveFields": False,
1626+
"SaveRadFields": False,
1627+
}
1628+
)
1629+
16171630
SweepHfss3D = dict(
16181631
{
16191632
"Type": "Interpolating",

src/ansys/aedt/core/modules/solve_setup.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ def get_solution_data(
432432
]
433433
else:
434434
setup_sweep_name = [i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0]]
435+
if report_category == "Fields":
436+
sweep = self._app.nominal_adaptive # Use last adaptive if no sweep is named explicitly.
435437
if setup_sweep_name:
436438
return self._app.post.get_solution_data(
437439
expressions=expressions,
@@ -442,7 +444,7 @@ def get_solution_data(
442444
context=context,
443445
polyline_points=polyline_points,
444446
math_formula=math_formula,
445-
setup_sweep_name=sweep,
447+
setup_sweep_name=sweep, # Should this be setup_sweep_name?
446448
)
447449
return None
448450

@@ -2873,7 +2875,7 @@ def create_single_point_sweep(
28732875
return sweepdata
28742876

28752877
@pyaedt_function_handler(sweepname="name", sweeptype="sweep_type")
2876-
def add_sweep(self, name=None, sweep_type="Interpolating"):
2878+
def add_sweep(self, name=None, sweep_type="Interpolating", **props):
28772879
"""Add a sweep to the project.
28782880
28792881
Parameters
@@ -2883,6 +2885,8 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
28832885
case a name is automatically assigned.
28842886
sweep_type : str, optional
28852887
Type of the sweep. The default is ``"Interpolating"``.
2888+
**props : Optional context-dependent keyword arguments can be passed
2889+
to the sweep setup
28862890
28872891
Returns
28882892
-------
@@ -2896,9 +2900,18 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
28962900
if not name:
28972901
name = generate_unique_name("Sweep")
28982902
if self.setuptype <= 4:
2899-
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type)
2903+
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type, props=props)
2904+
elif self.setuptype in [14, 30, 31]:
2905+
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type) # TODO: add , props=props)
2906+
else:
2907+
self._app.logger.warning("This method only applies to HFSS, Q2D, and Q3D.")
2908+
return False
29002909
sweep_n.create()
29012910
self.sweeps.append(sweep_n)
2911+
for setup in self.p_app.setups:
2912+
if self.name == setup.name:
2913+
setup.sweeps.append(sweep_n)
2914+
break
29022915
return sweep_n
29032916

29042917
@pyaedt_function_handler(sweepname="name")
@@ -4126,7 +4139,7 @@ def create_single_point_sweep(
41264139
return sweepdata
41274140

41284141
@pyaedt_function_handler(sweepname="name", sweeptype="sweep_type")
4129-
def add_sweep(self, name=None, sweep_type="Interpolating"):
4142+
def add_sweep(self, name=None, sweep_type="Interpolating", **props):
41304143
"""Add a sweep to the project.
41314144
41324145
Parameters
@@ -4150,6 +4163,16 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
41504163
name = generate_unique_name("Sweep")
41514164
if self.setuptype in [14, 30, 31]:
41524165
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type)
4166+
if self.setuptype == 7:
4167+
self._app.logger.warning("This method only applies to HFSS and Q3D. Use add_eddy_current_sweep method.")
4168+
return False
4169+
if self.setuptype <= 4:
4170+
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type, props=props)
4171+
elif self.setuptype in [14, 30, 31]:
4172+
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type, props=props)
4173+
else:
4174+
self._app.logger.warning("This method only applies to HFSS, Q2D, and Q3D.")
4175+
return False
41534176
sweep_n.create()
41544177
self.sweeps.append(sweep_n)
41554178
for setup in self.p_app.setups:

src/ansys/aedt/core/modules/solve_sweeps.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from ansys.aedt.core.modules.setup_templates import Sweep3DLayout
3838
from ansys.aedt.core.modules.setup_templates import SweepEddyCurrent
3939
from ansys.aedt.core.modules.setup_templates import SweepHfss3D
40+
from ansys.aedt.core.modules.setup_templates import SweepQ3D
4041
from ansys.aedt.core.modules.setup_templates import SweepSiwave
4142

4243
open3 = open
@@ -109,25 +110,35 @@ class SweepHFSS(object):
109110
def __init__(self, setup, name, sweep_type="Interpolating", props=None):
110111
self._app = setup
111112
self.oanalysis = setup.omodule
112-
self.props = {}
113113
self.setup_name = setup.name
114114
self.name = name
115+
self.props = copy.deepcopy(SweepHfss3D)
115116
if props:
116-
self.props = props
117+
if "RangeStep" in props.keys(): # LinearCount is the default sweep type. Change it if RangeStep is passed.
118+
if "RangeCount" in props.keys():
119+
self._app.p_app.logger.info(
120+
"Inconsistent arguments 'RangeCount' and 'RangeStep' passed to 'SweepHFSS',"
121+
)
122+
self._app.p_app.logger.info("Default remains 'LinearCount' sweep type.")
123+
else:
124+
self.props["RangeType"] = "LinearStep"
125+
for key, value in props.items():
126+
if key in self.props.keys():
127+
self.props[key] = value
128+
else:
129+
error_message = f"Parameter '{key}' is invalid and will be ignored."
130+
self._app.p_app.logger.warning(error_message)
131+
132+
if SequenceMatcher(None, sweep_type.lower(), "interpolating").ratio() > 0.8:
133+
sweep_type = "Interpolating"
134+
elif SequenceMatcher(None, sweep_type.lower(), "discrete").ratio() > 0.8:
135+
sweep_type = "Discrete"
136+
elif SequenceMatcher(None, sweep_type.lower(), "fast").ratio() > 0.8:
137+
sweep_type = "Fast"
117138
else:
118-
self.props = copy.deepcopy(SweepHfss3D)
119-
# for t in SweepHfss3D:
120-
# _tuple2dict(t, self.props)
121-
if SequenceMatcher(None, sweep_type.lower(), "interpolating").ratio() > 0.8:
122-
sweep_type = "Interpolating"
123-
elif SequenceMatcher(None, sweep_type.lower(), "discrete").ratio() > 0.8:
124-
sweep_type = "Discrete"
125-
elif SequenceMatcher(None, sweep_type.lower(), "fast").ratio() > 0.8:
126-
sweep_type = "Fast"
127-
else:
128-
warnings.warn("Invalid sweep type. `Interpolating` will be set as the default.")
129-
sweep_type = "Interpolating"
130-
self.props["Type"] = sweep_type
139+
warnings.warn("Invalid sweep type. `Interpolating` will be set as the default.")
140+
sweep_type = "Interpolating"
141+
self.props["Type"] = sweep_type
131142

132143
@property
133144
def is_solved(self):
@@ -612,17 +623,19 @@ class SweepMatrix(object):
612623
"""
613624

614625
def __init__(self, setup, name, sweep_type="Interpolating", props=None):
615-
self._app = setup
626+
self._app = setup # TODO: Remove sweep_type as an argument as it can be passed in props
616627
self.oanalysis = setup.omodule
617628
self.setup_name = setup.name
618629
self.name = name
619-
self.props = {}
630+
self.props = copy.deepcopy(SweepQ3D)
620631
if props:
621-
self.props = props
632+
for key, value in props.items():
633+
if key in self.props:
634+
self.props[key] = value
622635
else:
623636
self.props["Type"] = sweep_type
624637
if sweep_type == "Discrete":
625-
self.props["isenabled"] = True
638+
self.props["IsEnabled"] = True
626639
self.props["RangeType"] = "LinearCount"
627640
self.props["RangeStart"] = "2.5GHz"
628641
self.props["RangeStep"] = "1GHz"

src/ansys/aedt/core/visualization/post/common.py

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,28 @@ def available_report_solutions(self, report_category=None):
494494
report_category = self.available_report_types[0]
495495
if report_category:
496496
return list(self.oreportsetup.GetAvailableSolutions(report_category))
497-
return None # pragma: no cover
497+
return None
498+
499+
@pyaedt_function_handler() # pragma: no cover
500+
def _get_setup_from_sweep_name(self, sweep_name):
501+
if ":" not in sweep_name:
502+
sweep_names = [] # Look for sweep name in setups if the setup is not
503+
for s in self._app.setups: # passed explicitly in setup_sweep_name.
504+
for sweep in s.sweeps:
505+
this_name = s.name + " : " + sweep.name if sweep.name == sweep_name else None
506+
if this_name:
507+
sweep_names.append(this_name)
508+
if len(sweep_names) > 1:
509+
warning_str = "More than one sweep with name '{setup_sweep_name}' found. "
510+
warning_str += f"Returning '{sweep_names[0]}'."
511+
self.logger.warning(warning_str)
512+
return sweep_names[0]
513+
elif len(sweep_names) == 1:
514+
return sweep_names[0]
515+
else:
516+
return sweep_name # Nothing found, pass the sweep name through.
517+
else:
518+
return sweep_name
498519

499520
@pyaedt_function_handler()
500521
def _get_plot_inputs(self):
@@ -1211,7 +1232,12 @@ def create_report(
12111232
>>> m3d.release_desktop(False, False)
12121233
"""
12131234
if not setup_sweep_name:
1214-
setup_sweep_name = self._app.nominal_sweep
1235+
if report_category == "Fields":
1236+
setup_sweep_name = self._app.nominal_adaptive # Field report and no sweep name passed.
1237+
else:
1238+
setup_sweep_name = self._app.nominal_sweep
1239+
elif domain == "Sweep":
1240+
setup_sweep_name = self._get_setup_from_sweep_name(setup_sweep_name)
12151241
if not domain:
12161242
domain = "Sweep"
12171243
setup_name = setup_sweep_name.split(":")[0]
@@ -1654,6 +1680,8 @@ def create_report_from_configuration(
16541680
solution_name = self._app.nominal_sweep
16551681
else:
16561682
solution_name = self._app.nominal_adaptive
1683+
else:
1684+
solution_name = self._get_setup_from_sweep_name(solution_name) # If only the sweep name is passed.
16571685
if props.get("report_category", None) and props["report_category"] in TEMPLATES_BY_NAME:
16581686
if props.get("context", {"context": {}}).get("domain", "") == "Spectral":
16591687
report_temp = TEMPLATES_BY_NAME["Spectrum"]
@@ -1979,7 +2007,8 @@ def fields(self, expressions=None, setup=None, polyline=None):
19792007
>>> solutions = report.get_solution_data()
19802008
"""
19812009
if not setup:
1982-
setup = self._post_app._app.nominal_sweep
2010+
# setup = self._post_app._app.nominal_sweep
2011+
setup = self._post_app._app.nominal_adaptive
19832012
rep = None
19842013
if "Fields" in self._templates:
19852014
rep = ansys.aedt.core.visualization.report.field.Fields(self._post_app, "Fields", setup)
@@ -2111,7 +2140,7 @@ def rl_fields(self, expressions=None, setup=None, polyline=None):
21112140
return rep
21122141

21132142
@pyaedt_function_handler(setup_name="setup")
2114-
def far_field(self, expressions=None, setup=None, sphere_name=None, source_context=None):
2143+
def far_field(self, expressions=None, setup=None, sphere_name=None, source_context=None, **variations):
21152144
"""Create a Far Field Report object.
21162145
21172146
Parameters
@@ -2146,10 +2175,17 @@ def far_field(self, expressions=None, setup=None, sphere_name=None, source_conte
21462175
setup = self._post_app._app.nominal_sweep
21472176
rep = None
21482177
if "Far Fields" in self._templates:
2149-
rep = ansys.aedt.core.visualization.report.field.FarField(self._post_app, "Far Fields", setup)
2178+
rep = ansys.aedt.core.visualization.report.field.FarField(self._post_app, "Far Fields", setup, **variations)
21502179
rep.far_field_sphere = sphere_name
21512180
rep.source_context = source_context
2152-
rep.expressions = self._retrieve_default_expressions(expressions, rep, setup)
2181+
rep.report_type = "Radiation Pattern"
2182+
if expressions:
2183+
if type(expressions) == list:
2184+
rep.expressions = expressions
2185+
else:
2186+
rep.expressions = [expressions]
2187+
else:
2188+
rep.expressions = self._retrieve_default_expressions(expressions, rep, setup)
21532189
return rep
21542190

21552191
@pyaedt_function_handler(setup_name="setup", sphere_name="infinite_sphere")

src/ansys/aedt/core/visualization/report/field.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,19 @@ def near_field(self, value):
131131
class FarField(CommonReport):
132132
"""Provides for managing far field reports."""
133133

134-
def __init__(self, app, report_category, setup_name, expressions=None):
134+
def __init__(self, app, report_category, setup_name, expressions=None, **variations):
135135
CommonReport.__init__(self, app, report_category, setup_name, expressions)
136+
variation_defaults = {"Phi": ["All"], "Theta": ["All"], "Freq": ["Nominal"]}
136137
self.domain = "Sweep"
137138
self.primary_sweep = "Phi"
138139
self.secondary_sweep = "Theta"
139140
self.source_context = None
140141
self.source_group = None
141-
if "Phi" not in self.variations:
142-
self.variations["Phi"] = ["All"]
143-
if "Theta" not in self.variations:
144-
self.variations["Theta"] = ["All"]
145-
if "Freq" not in self.variations:
146-
self.variations["Freq"] = ["Nominal"]
142+
for key, default_value in variation_defaults.items():
143+
if key in variations:
144+
self.variations[key] = variations[key]
145+
else:
146+
self.variations[key] = default_value
147147

148148
@property
149149
def far_field_sphere(self):
Binary file not shown.

tests/system/general/test_11_Setup.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ def test_01_create_hfss_setup(self):
102102
max_delta_phase=8,
103103
custom_entries=[["1", "2", 0.03, 4]],
104104
)
105+
setup2 = self.aedtapp.create_setup(
106+
"MulitFreqSetup", MultipleAdaptiveFreqsSetup=["1GHz", "2GHz"], MaximumPasses=3
107+
)
108+
assert setup2.props["SolveType"] == "MultiFrequency"
109+
assert setup2.props["MaximumPasses"] == 3
105110

106111
def test_01b_create_hfss_sweep(self):
107112
self.aedtapp.save_project()
@@ -121,6 +126,21 @@ def test_01b_create_hfss_sweep(self):
121126
assert sweep3.props["Type"] == "Discrete"
122127
sweep4 = setup1.create_frequency_sweep("GHz", 23, 25, 401, sweep_type="Fast")
123128
assert sweep4.props["Type"] == "Fast"
129+
range_start = "1GHz"
130+
range_end = "2GHz"
131+
range_step = "0.5GHz"
132+
sweep5 = setup1.add_sweep(
133+
"DiscSweep5",
134+
sweep_type="Discrete",
135+
RangeStart=range_start,
136+
RangeEnd=range_end,
137+
RangeStep=range_step,
138+
SaveFields=True,
139+
)
140+
assert sweep5.props["Type"] == "Discrete"
141+
assert sweep5.props["RangeStart"] == range_start
142+
assert sweep5.props["RangeEnd"] == range_end
143+
assert sweep5.props["RangeStep"] == range_step
124144

125145
def test_01c_create_hfss_setup_auto_open(self):
126146
self.aedtapp.duplicate_design("auto_open")

0 commit comments

Comments
 (0)