differt.geometry.fermat_path_on_linear_objects#
- fermat_path_on_linear_objects(from_vertex, to_vertex, object_origins, object_vectors, *, steps=10, unroll=1, linesearch_steps=1, unroll_linesearch=1, implicit_diff=True)[source]#
Return the ray path between a pair of vertices, that reflects or diffracts on a given list of objects in between.
Linear objects are defined by a linear combination of zero or more (possibly orthogonal) vectors, plus a point in space (i.e., the origin).
E.g., a straight line can be defined by a point and a direction vector, while a plane can be defined by a point and two orthogonal direction vectors.
Because the size of
object_vectorsis determined by the object with the most dimensions, objects with fewer dimensions should have the extra vectors set to zero.Based on the Fermat principle, this method finds the ray path between the
fromandtovertices, that minimizes the total length of the path.While the method assumes that the objects are infinite, as for the
image method, choosing an appropriate origin can be important as it will be used as the initial point of the minimization procedure.Important
The current implementation uses the
fpt-jaxlibrary [4], which definesjax.custom_vjpfunctions for more efficient gradient computations, see the paper for more details. The implementation is not guaranteed to converge for all configurations of vertices and objects, and the user may need to tune the number of steps to achieve convergence.- Parameters:
from_vertex (
Float[ArrayLike, '*#batch 3']) –fromvertex, i.e., vertex from which the ray path starts. In a radio communications context, this is usually the transmitter position.to_vertex (
Float[ArrayLike, '*#batch 3']) –tovertex, i.e., vertex to which the ray path ends. In a radio communications context, this is usually the receiver position.object_origins (
Float[ArrayLike, '*#batch num_objects 3']) – Object origins, used as the initial guess of the minimization procedure.object_vectors (
Float[ArrayLike, '*#batch num_objects num_dims 3']) – Base vector(s) describing each object.steps (
int) – The number of optimization steps to perform.unroll (
int|bool) – Whether to unroll the optimization loop. Can be a boolean or an integer specifying the number of iterations to unroll, seejax.lax.scan.linesearch_steps (
int) – The number of line search steps to perform at each iteration.unroll_linesearch (
int|bool) – Whether to unroll the line search loop. Can be a boolean or an integer specifying the number of iterations to unroll, seejax.lax.scan.implicit_diff (
bool) – Whether to use implicit differentiation for computing the gradient. See [4] and its GitHub page for more details.
- Return type:
- Returns:
Intermediate ray path vertices obtained using Fermat’s principle.
Note
The paths do not contain the starting and ending vertices.
You can easily create the complete ray paths using
assemble_path:path = fermat_path_on_linear_objects(...) full_path = assemble_path( from_vertex, path, to_vertex, )
Examples
The following example shows how to use this method to find the ray paths undergoing a diffraction on the edge of a wall, then a reflection on a mirror, before reaching the receiver.
>>> from differt.geometry import Mesh, normalize, assemble_path >>> from differt.plotting import draw_markers, draw_paths, reuse >>> from differt.rt import fermat_path_on_linear_objects >>> >>> from_vertex = jnp.array([-2.0, 0.0, 0.0]) >>> to_vertex = jnp.array([0.0, 0.0, 0.0]) >>> wall = Mesh.plane( ... jnp.array([-1.0, 0.0, 0.0]), normal=jnp.array([1.0, 0.0, 0.0]) ... ) >>> mirror = Mesh.plane( ... jnp.array([1.0, 0.0, 0.0]), normal=jnp.array([1.0, 0.0, 0.0]) ... ) >>> object_origins = jnp.array([[-1.0, -0.5, 0.5], [1.0, 0.0, 0.0]]) >>> object_vectors = jnp.array([ ... [[0.0, 1.0, 0.0], [0.0, 0.0, 0.0]], # Edges only need one vector ... [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], # Planes need two vectors ... ]) >>> path = fermat_path_on_linear_objects( ... from_vertex, ... to_vertex, ... object_origins, ... object_vectors, ... ) >>> with reuse(backend="plotly") as fig: ... wall.plot(color="blue") ... mirror.plot(color="red") ... draw_paths( ... wall.triangle_edges[0, 1, ...], ... line_color="yellow", ... line_width=5, ... name="Edge", ... ) ... ... full_path = assemble_path( ... from_vertex, ... path, ... to_vertex, ... ) ... draw_paths(full_path, marker={"color": "green"}, name="Final path") ... markers = jnp.vstack((from_vertex, to_vertex)) ... draw_markers( ... markers, ... labels=["BS", "UE"], ... marker={"color": "black"}, ... name="BS/UE", ... ) ... fig.update_layout(scene_aspectmode="data") >>> fig