differt.geometry.fermat_path_on_linear_objects

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_vectors is 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 from and to vertices, 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-jax library [4], which defines jax.custom_vjp functions 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']) – from vertex, 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']) – to vertex, 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, see jax.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, see jax.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:

Float[Array, '*batch num_objects 3']

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