#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy
import pyvista
from typing import List
from MPSPlots.colormaps import blue_black_red
from PyMieSim.units import ureg, Length
[docs]
class FarFields():
r"""
Compute the far-field scattering pattern for the scatterer.
The far fields describe the behavior of the scattered electromagnetic waves at a large distance from the scatterer, where the waves can be approximated as planar.
The computed far fields represent the scattered electric field components in the directions parallel and perpendicular to the plane of incidence.
The far fields are computed as:
.. math::
\text{Fields} = E_{||}(\phi, \theta)^2, \, E_{\perp}(\phi, \theta)^2
These components represent the intensities of the scattered electric field in the parallel (\( E_{||} \)) and perpendicular (\( E_{\perp} \)) directions as functions of the spherical angles \( \phi \) (azimuthal) and \( \theta \) (polar).
The fields are expressed up to a constant phase factor:
.. math::
\exp{\left(-i k r \right)}
Where:
- :math:`k`: The wave number, related to the wavelength of the incident light.
- :math:`r`: The distance from the scatterer (assumed to be large in the far-field approximation).
Parameters
----------
sampling : int
The number of angular points used to sample the far-field pattern. A higher sampling value increases the angular resolution of the computed far-field intensities.
distance : Length, optional
The distance from the scatterer at which the far fields are evaluated. By default, this is set to 1 meter, but it can be adjusted to suit the specific experimental configuration.
Returns
-------
representations.FarField
An object containing the computed far-field components (parallel and perpendicular intensities), which represent the angular distribution of the scattered light in the far field.
Notes
-----
- The far-field approximation assumes that the distance from the scatterer is large enough that the curvature of the wavefronts can be neglected, and the fields can be treated as planar.
- This method is useful for determining the angular distribution of the scattered light, which is critical in applications such as radar cross-section analysis, optical scattering experiments, and remote sensing.
"""
def __init__(self, setup: object, sampling: int = 200, distance: Length = 1.0 * ureg.meter):
self.setup = setup
self.sampling = sampling
self.distance = distance
self.E_phi, self.E_theta, self.mesh = self.setup.get_farfields(
sampling=self.sampling, distance=self.distance
)
[docs]
def plot(
self,
unit_size: List[float] = (400, 400),
background_color: str = "white",
show_edges: bool = False,
colormap: str = blue_black_red,
opacity: float = 1.0,
show_axis_label: bool = False,
) -> None:
"""
Visualizes the Far field (in phi and theta vector projections) on a 3D plot.
Parameters
----------
unit_size : List[float]
The size of each subplot in pixels (width, height). Default is (400, 400).
background_color : str
The background color of the plot. Default is 'white'.
show_edges : bool
If True, displays the edges of the mesh. Default is False.
colormap : str
The colormap to use for scalar mapping. Default is 'blue_black_red'.
opacity : float
The opacity of the mesh. Default is 1.0.
show_axis_label : bool
If True, shows the axis labels. Default is False.
"""
cartesian = self.mesh.spherical_mesh.to_cartesian()
window_size = (unit_size[1] * 4, unit_size[0]) # Two subplots horizontally
scene = pyvista.Plotter(
theme=pyvista.themes.DocumentTheme(), window_size=window_size, shape=(1, 4)
)
scene.set_background(background_color)
repr = [self.E_phi.real, self.E_phi.imag, self.E_theta.real, self.E_theta.imag]
repr_label = ["phi real", "phi imag", "theta real", "theta imag"]
for idx, (label, field) in enumerate(zip(repr_label, repr)):
field = field.flatten(order="F").magnitude
mesh = pyvista.StructuredGrid(
cartesian.x.to("meter").magnitude,
cartesian.y.to("meter").magnitude,
cartesian.z.to("meter").magnitude
)
scene.subplot(0, idx)
max_abs = numpy.abs(field).max()
colormap_limits = [-max_abs, max_abs]
mapping = scene.add_mesh(
mesh,
cmap=colormap,
scalars=field,
opacity=opacity,
style="surface",
show_edges=show_edges,
clim=colormap_limits,
show_scalar_bar=False,
)
if "theta" in label:
self.add_theta_vector_to_3d_plot(scene=scene, radius=1.1, color="White")
if "phi" in label:
self.add_phi_vector_to_3d_plot(scene=scene, radius=1.1, color="White")
scene.add_axes_at_origin(labels_off=not show_axis_label)
scene.add_scalar_bar(mapper=mapping.mapper, title=f"{label} field")
scene.show()
[docs]
def add_theta_vector_to_3d_plot(
self,
scene: pyvista.Plotter,
n_points: int = 20,
opacity: float = 1.0,
radius: float = 1.0,
color: str = "black",
) -> None:
"""
Adds a vector field to the 3D plot, representing vectors in the theta direction.
Parameters
----------
scene : pyvista.Plotter
The 3D plotting scene to which the vectors will be added.
n_points : int
Number of points to generate along the theta and phi directions. Default is 100.
opacity : float
Opacity of the vectors. Default is 1.0.
radius : float
Radius at which to place the vectors. Default is 1.0.
color : str
Color of the vectors. Default is 'black'.
"""
theta = numpy.linspace(0, 360, n_points)
phi = numpy.linspace(180, 0, n_points)
# Convert spherical coordinates to Cartesian coordinates
x, y, z = pyvista.transform_vectors_sph_to_cart(theta=theta, phi=phi, r=radius, u=1, v=0, w=0)
# Combine the Cartesian coordinates into a vector array
vector_field = numpy.c_[x.ravel(), y.ravel(), z.ravel()]
# Create a structured grid from spherical coordinates
spherical_grid = pyvista.grid_from_sph_coords(theta, phi, radius)
spherical_grid.point_data["component"] = vector_field * 0.1
# Generate glyphs (arrows) for the vectors
glyphs = spherical_grid.glyph(
orient="component", scale="component", tolerance=0.005
)
# Add the vector glyphs to the scene
scene.add_mesh(glyphs, color=color, opacity=opacity)
[docs]
def add_phi_vector_to_3d_plot(
self,
scene: pyvista.Plotter,
n_points: int = 20,
opacity: float = 1.0,
radius: float = 1.0,
color: str = "black",
) -> None:
"""
Adds a vector field to the 3D plot, representing vectors in the phi direction.
Parameters
----------
scene : pyvista.Plotter
The 3D plotting scene to which the vectors will be added.
n_points : int
Number of points to generate along the theta and phi directions. Default is 100.
opacity : float
Opacity of the vectors. Default is 1.0.
radius : float
Radius at which to place the vectors. Default is 1.0.
color : str
Color of the vectors. Default is 'black'.
"""
theta = numpy.linspace(0, 360, n_points)
phi = numpy.linspace(180, 0, n_points)
# Convert spherical coordinates to Cartesian coordinates
x, y, z = pyvista.transform_vectors_sph_to_cart(theta=theta, phi=phi, r=radius, u=0, v=1, w=0)
# Combine the Cartesian coordinates into a vector array
vector_field = numpy.c_[x.ravel(), y.ravel(), z.ravel()]
# Create a structured grid from spherical coordinates
spherical_grid = pyvista.grid_from_sph_coords(theta, phi, radius)
spherical_grid.point_data["component"] = vector_field * 0.1
# Generate glyphs (arrows) for the vectors
glyphs = spherical_grid.glyph(
orient="component", scale="component", tolerance=0.005
)
# Add the vector glyphs to the scene
scene.add_mesh(glyphs, color=color, opacity=opacity)