Skip to content

Commit

Permalink
FEAT: Improve antenna array processing and plotting (#4626)
Browse files Browse the repository at this point in the history
Co-authored-by: Samuel Lopez <[email protected]>
Co-authored-by: Samuelopez-ansys <[email protected]>
Co-authored-by: Kathy Pippert <[email protected]>
  • Loading branch information
4 people authored May 13, 2024
1 parent a5fbb84 commit 2e21c89
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 58 deletions.
5 changes: 5 additions & 0 deletions _unittest/test_12_PostProcessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ def test_71_antenna_plot(self, field_test):
title="Contour at {}Hz".format(ffdata.frequency),
image_path=os.path.join(self.local_scratch.path, "contour.jpg"),
convert_to_db=True,
show=False,
)
assert os.path.exists(os.path.join(self.local_scratch.path, "contour.jpg"))

Expand Down Expand Up @@ -686,6 +687,7 @@ def test_72_antenna_plot(self, array_test):
title="Contour at {}Hz".format(ffdata.frequency),
image_path=os.path.join(self.local_scratch.path, "contour.jpg"),
convert_to_db=True,
show=False,
)
assert os.path.exists(os.path.join(self.local_scratch.path, "contour.jpg"))

Expand All @@ -695,6 +697,7 @@ def test_72_antenna_plot(self, array_test):
secondary_sweep_value=[-180, -75, 75],
title="Azimuth at {}Hz".format(ffdata.frequency),
image_path=os.path.join(self.local_scratch.path, "2d1.jpg"),
show=False,
)
assert os.path.exists(os.path.join(self.local_scratch.path, "2d1.jpg"))
ffdata.plot_2d_cut(
Expand All @@ -703,6 +706,7 @@ def test_72_antenna_plot(self, array_test):
secondary_sweep_value=30,
title="Azimuth at {}Hz".format(ffdata.frequency),
image_path=os.path.join(self.local_scratch.path, "2d2.jpg"),
show=False,
)

assert os.path.exists(os.path.join(self.local_scratch.path, "2d2.jpg"))
Expand All @@ -725,6 +729,7 @@ def test_72_antenna_plot(self, array_test):
title="Contour at {}Hz".format(ffdata1.frequency),
image_path=os.path.join(self.local_scratch.path, "contour1.jpg"),
convert_to_db=True,
show=False,
)
assert os.path.exists(os.path.join(self.local_scratch.path, "contour1.jpg"))

Expand Down
61 changes: 61 additions & 0 deletions pyaedt/application/analysis_hf.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,64 @@ def export_touchstone(
impedance=impedance,
comments=gamma_impedance_comments,
)


def phase_expression(m, n, theta_name="theta_scan", phi_name="phi_scan"):
"""Return an expression for the source phase angle in a rectangular antenna array.
Parameters
----------
m : int, required
Index of the rectangular antenna array element in the x direction.
n : int, required
Index of the rectangular antenna array element in the y direction.
theta_name : str, optional
Postprocessing variable name in HFSS to use for the
theta component of the phase angle expression. The default is ``"theta_scan"``.
phi_name : str, optional
Postprocessing variable name in HFSS to use to generate
the phi component of the phase angle expression. The default is ``"phi_scan"``
Returns
-------
str
Phase angle expression for the (m,n) source of
the (m,n) antenna array element.
"""
# px is the term for the phase variation in the x direction.
# py is the term for the phase variation in the y direction.

if n > 0:
add_char = " + "
else:
add_char = " - "
if m == 0:
px = ""
elif m == -1:
px = "-pi*sin(theta_scan)*cos(phi_scan)"
elif m == 1:
px = "pi*sin(theta_scan)*cos(phi_scan)"
else:
px = str(m) + "*pi*sin(theta_scan)*cos(phi_scan)"
if n == 0:
py = ""
elif n == -1 or n == 1:
py = "pi*sin(theta_scan)*sin(phi_scan)"

else:
py = str(abs(n)) + "*pi*sin(theta_scan)*sin(phi_scan)"
if m == 0:
if n == 0:
return "0"
elif n < 0:
return "-" + py
else:
return py
elif n == 0:
if m == 0:
return "0"
else:
return px
else:
return px + add_char + py
92 changes: 67 additions & 25 deletions pyaedt/generic/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,37 @@
from matplotlib.path import Path
import matplotlib.pyplot as plt

rc_params = {
"axes.titlesize": 26, # Use these default settings for Matplotlb axes.
"axes.labelsize": 20, # Apply the settings only in this module.
"xtick.labelsize": 18,
"ytick.labelsize": 18,
}

except ImportError:
warnings.warn(
"The Matplotlib module is required to run some functionalities of PostProcess.\n"
"Install with \n\npip install matplotlib\n\nRequires CPython."
)
except Exception:
pass
warnings.warn("Unknown error occurred while attempting to import Matplotlib.")


# Override default settings for matplotlib
def update_plot_settings(func, *args, **kwargs):
if callable(func):

def wrapper(*args, **kwargs):
default_rc_params = plt.rcParams.copy()
plt.rcParams.update(rc_params) # Apply new settings.
out = func(*args, **kwargs)
plt.rcParams.update(default_rc_params)
return out

else:
wrapper = None
raise TypeError("First argument must be callable.")
return wrapper


@pyaedt_function_handler()
Expand Down Expand Up @@ -100,6 +124,7 @@ def is_float(istring):
try:
return float(istring.strip())
except Exception:
warnings.warn("Unable to convert '" + istring.strip() + "' to a float.")
return 0


Expand Down Expand Up @@ -293,10 +318,11 @@ def _parse_streamline(filepath):


@pyaedt_function_handler()
@update_plot_settings
def plot_polar_chart(
plot_data, size=(2000, 1000), show_legend=True, xlabel="", ylabel="", title="", snapshot_path=None
plot_data, size=(2000, 1000), show_legend=True, xlabel="", ylabel="", title="", snapshot_path=None, show=True
):
"""Create a matplotlib polar plot based on a list of data.
"""Create a Matplotlib polar plot based on a list of data.
Parameters
----------
Expand All @@ -312,9 +338,17 @@ def plot_polar_chart(
ylabel : str
Plot Y label.
title : str
Plot Title label.
Plot title label.
snapshot_path : str
Full path to image file if a snapshot is needed.
Full path to the image file if a snapshot is needed.
show : bool, optional
Whether to render the figure. The default is ``True``. If ``False``, the
figure is not drawn.
Returns
-------
:class:`matplotlib.pyplot.Figure`
Matplotlib figure object.
"""
dpi = 100.0

Expand Down Expand Up @@ -344,14 +378,15 @@ def plot_polar_chart(
fig.set_size_inches(size[0] / dpi, size[1] / dpi)
if snapshot_path:
fig.savefig(snapshot_path)
else:
if show:
fig.show()
return fig


@pyaedt_function_handler()
@update_plot_settings
def plot_3d_chart(plot_data, size=(2000, 1000), xlabel="", ylabel="", title="", snapshot_path=None):
"""Create a matplotlib 3D plot based on a list of data.
"""Create a Matplotlib 3D plot based on a list of data.
Parameters
----------
Expand All @@ -371,8 +406,8 @@ def plot_3d_chart(plot_data, size=(2000, 1000), xlabel="", ylabel="", title="",
Returns
-------
:class:`matplotlib.plt`
Matplotlib fig object.
:class:`matplotlib.pyplot.Figure`
Matplotlib figure object.
"""
dpi = 100.0

Expand Down Expand Up @@ -403,9 +438,9 @@ def plot_3d_chart(plot_data, size=(2000, 1000), xlabel="", ylabel="", title="",


@pyaedt_function_handler()
@update_plot_settings
def plot_2d_chart(plot_data, size=(2000, 1000), show_legend=True, xlabel="", ylabel="", title="", snapshot_path=None):
"""Create a matplotlib plot based on a list of data.
"""Create a Matplotlib plot based on a list of data.
Parameters
----------
plot_data : list of list
Expand All @@ -427,8 +462,8 @@ def plot_2d_chart(plot_data, size=(2000, 1000), show_legend=True, xlabel="", yla
Returns
-------
:class:`matplotlib.plt`
Matplotlib fig object.
:class:`matplotlib.pyplot.Figure`
Matplotlib figure object.
"""
dpi = 100.0
figsize = (size[0] / dpi, size[1] / dpi)
Expand Down Expand Up @@ -460,6 +495,7 @@ def plot_2d_chart(plot_data, size=(2000, 1000), show_legend=True, xlabel="", yla


@pyaedt_function_handler()
@update_plot_settings
def plot_matplotlib(
plot_data,
size=(2000, 1000),
Expand Down Expand Up @@ -511,8 +547,8 @@ def plot_matplotlib(
Returns
-------
:class:`matplotlib.plt`
Matplotlib fig object.
:class:`matplotlib.pyplot.Figure`
Matplotlib Figure object.
"""
dpi = 100.0
figsize = (size[0] / dpi, size[1] / dpi)
Expand Down Expand Up @@ -569,14 +605,17 @@ def plot_matplotlib(

if snapshot_path:
plt.savefig(snapshot_path)
elif show:
if show:
plt.show()
return plt
return fig


@pyaedt_function_handler()
def plot_contour(qty_to_plot, x, y, size=(2000, 1600), xlabel="", ylabel="", title="", levels=64, snapshot_path=None):
"""Create a matplotlib contour plot.
@update_plot_settings
def plot_contour(
qty_to_plot, x, y, size=(2000, 1600), xlabel="", ylabel="", title="", levels=64, snapshot_path=None, show=True
):
"""Create a Matplotlib contour plot.
Parameters
----------
Expand All @@ -595,14 +634,17 @@ def plot_contour(qty_to_plot, x, y, size=(2000, 1600), xlabel="", ylabel="", tit
title : str, optional
Plot Title Label. Default is `""`.
levels : int, optional
Color map levels. Default is `64`.
Color map levels. The default is ``64``.
snapshot_path : str, optional
Full path to image to save. Default is None.
Full path to save the image save. The default is ``None``.
show : bool, optional
Whether to render the figure. The default is ``True``. If
``False``, the image is not drawn.
Returns
-------
:class:`matplotlib.plt`
Matplotlib fig object.
:class:`matplotlib.pyplot.Figure`
Matplotlib figure object.
"""
dpi = 100.0
figsize = (size[0] / dpi, size[1] / dpi)
Expand All @@ -625,9 +667,9 @@ def plot_contour(qty_to_plot, x, y, size=(2000, 1600), xlabel="", ylabel="", tit
plt.colorbar()
if snapshot_path:
plt.savefig(snapshot_path)
else:
if show:
plt.show()
return plt
return fig


class ObjClass(object):
Expand Down
26 changes: 22 additions & 4 deletions pyaedt/hfss.py
Original file line number Diff line number Diff line change
Expand Up @@ -5237,13 +5237,14 @@ def set_differential_pair(

@pyaedt_function_handler(array_name="name", json_file="input_data")
def add_3d_component_array_from_json(self, input_data, name=None):
"""Add or edit a 3D component array from a JSON file or TOML file.
"""Add or edit a 3D component array from a JSON file, TOML file, or dictionary.
The 3D component is placed in the layout if it is not present.
Parameters
----------
input_data : str, dict
Full path to either the JSON file or dictionary containing the array information.
Full path to either the JSON file, TOML file, or the dictionary
containing the array information.
name : str, optional
Name of the boundary to add or edit.
Expand Down Expand Up @@ -5417,8 +5418,11 @@ def get_antenna_ffd_solution_data(
sphere=None,
variations=None,
overwrite=True,
link_to_hfss=True,
):
"""Export antennas parameters to Far Field Data (FFD) files and return the ``FfdSolutionDataExporter`` object.
"""Export the antenna parameters to Far Field Data (FFD) files and return an
instance of the
``FfdSolutionDataExporter`` object.
For phased array cases, only one phased array is calculated.
Expand All @@ -5435,12 +5439,20 @@ def get_antenna_ffd_solution_data(
Variation dictionary.
overwrite : bool, optional
Whether to overwrite FFD files. The default is ``True``.
link_to_hfss : bool, optional
Whether to return an instance of the
:class:`pyaedt.modules.solutions.FfdSolutionDataExporter` class,
which requires a connection to an instance of the :class:`Hfss` class.
The default is `` True``. If ``False``, returns an instance of
:class:`pyaedt.modules.solutions.FfdSolutionData` class, which is
independent from the running HFSS instance.
Returns
-------
:class:`pyaedt.modules.solutions.FfdSolutionDataExporter`
SolutionData object.
"""
from pyaedt.modules.solutions import FfdSolutionData
from pyaedt.modules.solutions import FfdSolutionDataExporter

if not variations:
Expand All @@ -5467,14 +5479,20 @@ def get_antenna_ffd_solution_data(
)
self.logger.info("Far field sphere %s is created.", setup)

return FfdSolutionDataExporter(
ffd = FfdSolutionDataExporter(
self,
sphere_name=sphere,
setup_name=setup,
frequencies=frequencies,
variations=variations,
overwrite=overwrite,
)
if link_to_hfss:
return ffd
else:
eep_file = ffd.eep_files
frequencies = ffd.frequencies
return FfdSolutionData(frequencies=frequencies, eep_files=eep_file)

@pyaedt_function_handler()
def set_material_threshold(self, threshold=100000):
Expand Down
Loading

0 comments on commit 2e21c89

Please sign in to comment.