Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Improve antenna array processing and plotting #4626

Merged
merged 35 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
221238a
Add array_element_phase() method to analysis_hf.py
Devin-Crawford Apr 24, 2024
26095f6
Update for ant FF post-processing
Devin-Crawford May 3, 2024
4601e8e
Update solutions.py
Devin-Crawford May 3, 2024
5a4d017
Merge remote-tracking branch 'origin/main' into add/antenna-methods
Devin-Crawford May 3, 2024
2c15cfa
Fix Iron Python comma error
Devin-Crawford May 4, 2024
9117046
Fix Iron Python comma error
Devin-Crawford May 4, 2024
1795588
Merge branch 'main' into add/antenna-methods
Devin-Crawford May 4, 2024
1e91b88
Merge branch 'main' into add/antenna-methods
Samuelopez-ansys May 6, 2024
5151030
Import matplotlib if not ironpython
Samuelopez-ansys May 6, 2024
e21ba18
Merge remote-tracking branch 'origin/add/antenna-methods' into add/an…
Samuelopez-ansys May 6, 2024
84b67ec
Test unittest
Samuelopez-ansys May 8, 2024
15fe8d0
Replace logger with warning
Samuelopez-ansys May 8, 2024
e23a516
Replace logger with warning
Samuelopez-ansys May 8, 2024
37c5aab
Remove post processing tests
Samuelopez-ansys May 8, 2024
827ed05
Merge branch 'main' into add/antenna-methods
Samuelopez-ansys May 8, 2024
8dcbf5c
Add post processing 1 test
Samuelopez-ansys May 8, 2024
b5693d4
Fix UT
Samuelopez-ansys May 8, 2024
5c4bcde
Fix UT
Samuelopez-ansys May 8, 2024
a6ce50d
Fix UT
Samuelopez-ansys May 8, 2024
32be8b3
Fix UT
Samuelopez-ansys May 8, 2024
e56e142
Fix UT
Samuelopez-ansys May 9, 2024
3e84f54
Fix UT
Samuelopez-ansys May 9, 2024
e31a33a
Fix UT
Samuelopez-ansys May 9, 2024
87ee574
Fix UT
Samuelopez-ansys May 9, 2024
f3aea68
Do not show contour plot in the unit test
Samuelopez-ansys May 9, 2024
2dd6e0d
Do not show contour plot in the unit test
Samuelopez-ansys May 9, 2024
9b17634
Do not show contour plot in the unit test
Samuelopez-ansys May 9, 2024
400a1f0
Uncomment YAML
Samuelopez-ansys May 9, 2024
2f9a381
Uncomment YAML
Samuelopez-ansys May 9, 2024
d8a281f
Uncomment YAML
Samuelopez-ansys May 9, 2024
3574747
Uncomment YAML
Samuelopez-ansys May 9, 2024
1c56d4b
Uncomment YAML
Samuelopez-ansys May 9, 2024
8cb438b
Apply suggestions from code review
Devin-Crawford May 13, 2024
65a2f27
Merge branch 'main' into add/antenna-methods
Devin-Crawford May 13, 2024
2dbf369
Update pyaedt/generic/plot.py
Devin-Crawford May 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading