Skip to content

Commit

Permalink
FIX: Enable field plotting with discrete sweep (#5683)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
5 people authored Feb 7, 2025
1 parent 29b7d2f commit 66716fc
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 51 deletions.
10 changes: 5 additions & 5 deletions src/ansys/aedt/core/hfss.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,8 +814,8 @@ def create_setup(self, name="MySetupAuto", setup_type=None, **kwargs):
"""Create an analysis setup for HFSS.
Optional arguments are passed along with ``setup_type`` and ``name``. Keyword
names correspond to the ``setup_type`` corresponding to the native AEDT API.
The list of keywords here is not exhaustive.
names correspond to keyword for the ``setup_type`` as defined in
the native AEDT API.
.. note::
This method overrides the ``Analysis.setup()`` method for the HFSS app.
Expand Down Expand Up @@ -860,9 +860,9 @@ def create_setup(self, name="MySetupAuto", setup_type=None, **kwargs):
setup.auto_update = False
for arg_name, arg_value in kwargs.items():
if setup[arg_name] is not None:
if arg_name == "MultipleAdaptiveFreqsSetup":
setup[arg_name].delete_all()
if isinstance(arg_value, list):
if arg_name == "MultipleAdaptiveFreqsSetup": # A list of frequency values is passed if
setup[arg_name].delete_all() # the default convergence criteria are to be
if isinstance(arg_value, list): # used.
for i in arg_value:
setup[arg_name][i] = [0.02]
else:
Expand Down
13 changes: 13 additions & 0 deletions src/ansys/aedt/core/modules/setup_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,19 @@ def HFSS3DLayout_AdaptiveFrequencyData(freq):

TR = {}

# Default sweep settings for Q3D
SweepQ3D = dict(
{
"IsEnabled": True,
"RangeType": "LinearStep",
"RangeStart": "1GHz",
"RangeEnd": "10GHz",
"Type": "Discrete",
"SaveFields": False,
"SaveRadFields": False,
}
)

SweepHfss3D = dict(
{
"Type": "Interpolating",
Expand Down
31 changes: 27 additions & 4 deletions src/ansys/aedt/core/modules/solve_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,8 @@ def get_solution_data(
]
else:
setup_sweep_name = [i for i in self._app.existing_analysis_sweeps if self.name == i.split(" : ")[0]]
if report_category == "Fields":
sweep = self._app.nominal_adaptive # Use last adaptive if no sweep is named explicitly.
if setup_sweep_name:
return self._app.post.get_solution_data(
expressions=expressions,
Expand All @@ -442,7 +444,7 @@ def get_solution_data(
context=context,
polyline_points=polyline_points,
math_formula=math_formula,
setup_sweep_name=sweep,
setup_sweep_name=sweep, # Should this be setup_sweep_name?
)
return None

Expand Down Expand Up @@ -2873,7 +2875,7 @@ def create_single_point_sweep(
return sweepdata

@pyaedt_function_handler(sweepname="name", sweeptype="sweep_type")
def add_sweep(self, name=None, sweep_type="Interpolating"):
def add_sweep(self, name=None, sweep_type="Interpolating", **props):
"""Add a sweep to the project.
Parameters
Expand All @@ -2883,6 +2885,8 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
case a name is automatically assigned.
sweep_type : str, optional
Type of the sweep. The default is ``"Interpolating"``.
**props : Optional context-dependent keyword arguments can be passed
to the sweep setup
Returns
-------
Expand All @@ -2896,9 +2900,18 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
if not name:
name = generate_unique_name("Sweep")
if self.setuptype <= 4:
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type)
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type, props=props)
elif self.setuptype in [14, 30, 31]:
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type) # TODO: add , props=props)
else:
self._app.logger.warning("This method only applies to HFSS, Q2D, and Q3D.")
return False
sweep_n.create()
self.sweeps.append(sweep_n)
for setup in self.p_app.setups:
if self.name == setup.name:
setup.sweeps.append(sweep_n)
break
return sweep_n

@pyaedt_function_handler(sweepname="name")
Expand Down Expand Up @@ -4126,7 +4139,7 @@ def create_single_point_sweep(
return sweepdata

@pyaedt_function_handler(sweepname="name", sweeptype="sweep_type")
def add_sweep(self, name=None, sweep_type="Interpolating"):
def add_sweep(self, name=None, sweep_type="Interpolating", **props):
"""Add a sweep to the project.
Parameters
Expand All @@ -4150,6 +4163,16 @@ def add_sweep(self, name=None, sweep_type="Interpolating"):
name = generate_unique_name("Sweep")
if self.setuptype in [14, 30, 31]:
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type)
if self.setuptype == 7:
self._app.logger.warning("This method only applies to HFSS and Q3D. Use add_eddy_current_sweep method.")
return False
if self.setuptype <= 4:
sweep_n = SweepHFSS(self, name=name, sweep_type=sweep_type, props=props)
elif self.setuptype in [14, 30, 31]:
sweep_n = SweepMatrix(self, name=name, sweep_type=sweep_type, props=props)
else:
self._app.logger.warning("This method only applies to HFSS, Q2D, and Q3D.")
return False
sweep_n.create()
self.sweeps.append(sweep_n)
for setup in self.p_app.setups:
Expand Down
51 changes: 32 additions & 19 deletions src/ansys/aedt/core/modules/solve_sweeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from ansys.aedt.core.modules.setup_templates import Sweep3DLayout
from ansys.aedt.core.modules.setup_templates import SweepEddyCurrent
from ansys.aedt.core.modules.setup_templates import SweepHfss3D
from ansys.aedt.core.modules.setup_templates import SweepQ3D
from ansys.aedt.core.modules.setup_templates import SweepSiwave

open3 = open
Expand Down Expand Up @@ -109,25 +110,35 @@ class SweepHFSS(object):
def __init__(self, setup, name, sweep_type="Interpolating", props=None):
self._app = setup
self.oanalysis = setup.omodule
self.props = {}
self.setup_name = setup.name
self.name = name
self.props = copy.deepcopy(SweepHfss3D)
if props:
self.props = props
if "RangeStep" in props.keys(): # LinearCount is the default sweep type. Change it if RangeStep is passed.
if "RangeCount" in props.keys():
self._app.p_app.logger.info(
"Inconsistent arguments 'RangeCount' and 'RangeStep' passed to 'SweepHFSS',"
)
self._app.p_app.logger.info("Default remains 'LinearCount' sweep type.")
else:
self.props["RangeType"] = "LinearStep"
for key, value in props.items():
if key in self.props.keys():
self.props[key] = value
else:
error_message = f"Parameter '{key}' is invalid and will be ignored."
self._app.p_app.logger.warning(error_message)

if SequenceMatcher(None, sweep_type.lower(), "interpolating").ratio() > 0.8:
sweep_type = "Interpolating"
elif SequenceMatcher(None, sweep_type.lower(), "discrete").ratio() > 0.8:
sweep_type = "Discrete"
elif SequenceMatcher(None, sweep_type.lower(), "fast").ratio() > 0.8:
sweep_type = "Fast"
else:
self.props = copy.deepcopy(SweepHfss3D)
# for t in SweepHfss3D:
# _tuple2dict(t, self.props)
if SequenceMatcher(None, sweep_type.lower(), "interpolating").ratio() > 0.8:
sweep_type = "Interpolating"
elif SequenceMatcher(None, sweep_type.lower(), "discrete").ratio() > 0.8:
sweep_type = "Discrete"
elif SequenceMatcher(None, sweep_type.lower(), "fast").ratio() > 0.8:
sweep_type = "Fast"
else:
warnings.warn("Invalid sweep type. `Interpolating` will be set as the default.")
sweep_type = "Interpolating"
self.props["Type"] = sweep_type
warnings.warn("Invalid sweep type. `Interpolating` will be set as the default.")
sweep_type = "Interpolating"
self.props["Type"] = sweep_type

@property
def is_solved(self):
Expand Down Expand Up @@ -612,17 +623,19 @@ class SweepMatrix(object):
"""

def __init__(self, setup, name, sweep_type="Interpolating", props=None):
self._app = setup
self._app = setup # TODO: Remove sweep_type as an argument as it can be passed in props
self.oanalysis = setup.omodule
self.setup_name = setup.name
self.name = name
self.props = {}
self.props = copy.deepcopy(SweepQ3D)
if props:
self.props = props
for key, value in props.items():
if key in self.props:
self.props[key] = value
else:
self.props["Type"] = sweep_type
if sweep_type == "Discrete":
self.props["isenabled"] = True
self.props["IsEnabled"] = True
self.props["RangeType"] = "LinearCount"
self.props["RangeStart"] = "2.5GHz"
self.props["RangeStep"] = "1GHz"
Expand Down
48 changes: 42 additions & 6 deletions src/ansys/aedt/core/visualization/post/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,28 @@ def available_report_solutions(self, report_category=None):
report_category = self.available_report_types[0]
if report_category:
return list(self.oreportsetup.GetAvailableSolutions(report_category))
return None # pragma: no cover
return None

@pyaedt_function_handler() # pragma: no cover
def _get_setup_from_sweep_name(self, sweep_name):
if ":" not in sweep_name:
sweep_names = [] # Look for sweep name in setups if the setup is not
for s in self._app.setups: # passed explicitly in setup_sweep_name.
for sweep in s.sweeps:
this_name = s.name + " : " + sweep.name if sweep.name == sweep_name else None
if this_name:
sweep_names.append(this_name)
if len(sweep_names) > 1:
warning_str = "More than one sweep with name '{setup_sweep_name}' found. "
warning_str += f"Returning '{sweep_names[0]}'."
self.logger.warning(warning_str)
return sweep_names[0]
elif len(sweep_names) == 1:
return sweep_names[0]
else:
return sweep_name # Nothing found, pass the sweep name through.
else:
return sweep_name

@pyaedt_function_handler()
def _get_plot_inputs(self):
Expand Down Expand Up @@ -1211,7 +1232,12 @@ def create_report(
>>> m3d.release_desktop(False, False)
"""
if not setup_sweep_name:
setup_sweep_name = self._app.nominal_sweep
if report_category == "Fields":
setup_sweep_name = self._app.nominal_adaptive # Field report and no sweep name passed.
else:
setup_sweep_name = self._app.nominal_sweep
elif domain == "Sweep":
setup_sweep_name = self._get_setup_from_sweep_name(setup_sweep_name)
if not domain:
domain = "Sweep"
setup_name = setup_sweep_name.split(":")[0]
Expand Down Expand Up @@ -1654,6 +1680,8 @@ def create_report_from_configuration(
solution_name = self._app.nominal_sweep
else:
solution_name = self._app.nominal_adaptive
else:
solution_name = self._get_setup_from_sweep_name(solution_name) # If only the sweep name is passed.
if props.get("report_category", None) and props["report_category"] in TEMPLATES_BY_NAME:
if props.get("context", {"context": {}}).get("domain", "") == "Spectral":
report_temp = TEMPLATES_BY_NAME["Spectrum"]
Expand Down Expand Up @@ -1979,7 +2007,8 @@ def fields(self, expressions=None, setup=None, polyline=None):
>>> solutions = report.get_solution_data()
"""
if not setup:
setup = self._post_app._app.nominal_sweep
# setup = self._post_app._app.nominal_sweep
setup = self._post_app._app.nominal_adaptive
rep = None
if "Fields" in self._templates:
rep = ansys.aedt.core.visualization.report.field.Fields(self._post_app, "Fields", setup)
Expand Down Expand Up @@ -2111,7 +2140,7 @@ def rl_fields(self, expressions=None, setup=None, polyline=None):
return rep

@pyaedt_function_handler(setup_name="setup")
def far_field(self, expressions=None, setup=None, sphere_name=None, source_context=None):
def far_field(self, expressions=None, setup=None, sphere_name=None, source_context=None, **variations):
"""Create a Far Field Report object.
Parameters
Expand Down Expand Up @@ -2146,10 +2175,17 @@ def far_field(self, expressions=None, setup=None, sphere_name=None, source_conte
setup = self._post_app._app.nominal_sweep
rep = None
if "Far Fields" in self._templates:
rep = ansys.aedt.core.visualization.report.field.FarField(self._post_app, "Far Fields", setup)
rep = ansys.aedt.core.visualization.report.field.FarField(self._post_app, "Far Fields", setup, **variations)
rep.far_field_sphere = sphere_name
rep.source_context = source_context
rep.expressions = self._retrieve_default_expressions(expressions, rep, setup)
rep.report_type = "Radiation Pattern"
if expressions:
if type(expressions) == list:
rep.expressions = expressions
else:
rep.expressions = [expressions]
else:
rep.expressions = self._retrieve_default_expressions(expressions, rep, setup)
return rep

@pyaedt_function_handler(setup_name="setup", sphere_name="infinite_sphere")
Expand Down
14 changes: 7 additions & 7 deletions src/ansys/aedt/core/visualization/report/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,19 @@ def near_field(self, value):
class FarField(CommonReport):
"""Provides for managing far field reports."""

def __init__(self, app, report_category, setup_name, expressions=None):
def __init__(self, app, report_category, setup_name, expressions=None, **variations):
CommonReport.__init__(self, app, report_category, setup_name, expressions)
variation_defaults = {"Phi": ["All"], "Theta": ["All"], "Freq": ["Nominal"]}
self.domain = "Sweep"
self.primary_sweep = "Phi"
self.secondary_sweep = "Theta"
self.source_context = None
self.source_group = None
if "Phi" not in self.variations:
self.variations["Phi"] = ["All"]
if "Theta" not in self.variations:
self.variations["Theta"] = ["All"]
if "Freq" not in self.variations:
self.variations["Freq"] = ["Nominal"]
for key, default_value in variation_defaults.items():
if key in variations:
self.variations[key] = variations[key]
else:
self.variations[key] = default_value

@property
def far_field_sphere(self):
Expand Down
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/system/general/test_11_Setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ def test_01_create_hfss_setup(self):
max_delta_phase=8,
custom_entries=[["1", "2", 0.03, 4]],
)
setup2 = self.aedtapp.create_setup(
"MulitFreqSetup", MultipleAdaptiveFreqsSetup=["1GHz", "2GHz"], MaximumPasses=3
)
assert setup2.props["SolveType"] == "MultiFrequency"
assert setup2.props["MaximumPasses"] == 3

def test_01b_create_hfss_sweep(self):
self.aedtapp.save_project()
Expand All @@ -121,6 +126,21 @@ def test_01b_create_hfss_sweep(self):
assert sweep3.props["Type"] == "Discrete"
sweep4 = setup1.create_frequency_sweep("GHz", 23, 25, 401, sweep_type="Fast")
assert sweep4.props["Type"] == "Fast"
range_start = "1GHz"
range_end = "2GHz"
range_step = "0.5GHz"
sweep5 = setup1.add_sweep(
"DiscSweep5",
sweep_type="Discrete",
RangeStart=range_start,
RangeEnd=range_end,
RangeStep=range_step,
SaveFields=True,
)
assert sweep5.props["Type"] == "Discrete"
assert sweep5.props["RangeStart"] == range_start
assert sweep5.props["RangeEnd"] == range_end
assert sweep5.props["RangeStep"] == range_step

def test_01c_create_hfss_setup_auto_open(self):
self.aedtapp.duplicate_design("auto_open")
Expand Down
Loading

0 comments on commit 66716fc

Please sign in to comment.