Quickstart

Installation

While installing DiffeRT2d and its dependencies on your global Python is fine, we recommend using a virtual environment (e.g., venv) for a local installation.

Dependencies

DiffeRT2d uses JAX for automatic differentation, which in turn may use (or not) CUDA for GPU acceleration.

If needed, please refer to JAX’s installation guidelines for more details.

Pip Install

The recommended way to install the latest release is to use pip:

pip install differt2d

Install From Repository

An alternative way to install DiffeRT2d is to clone the git repository, and install from there: read the contributing guide to know how.

Creating your first scene

DiffeRT2d is meant to perform Ray Tracing simulations on two dimensional scenes.

A scene is simply defined as a collection of objects (see Object), and some transmitters / receivers (see Point).

By default, DiffeRT2d only provides a few basic objects (like straight walls, see Wall), but anyone can create more complex objects, as long as the correct protocols are implemented.

A few scenes are already provided with this library, and can be created with one-liner class methods, e.g.:

from differt2d.scene import Scene

scene = Scene.square_scene()

Modifying a scene

Objects can easily be added to a scene, and we use jax.numpy for internal arrays storage. JAX provides the same API as numpy, with additional utilities regarding automatic differention, which is pretty useful for optimization problems.

import jax.numpy as jnp

from differt2d.geometry import Wall

wall = Wall(xys=jnp.array([[0.8, 0.2], [0.8, 0.8]]))
scene = scene.add_objects(wall)

Warning

Most classes are immutable PyTrees, meaning that mutation is performed by returning a new class instance.

Plotting utils

For testing purposes, most classes implement the Plottable protocol, which means you can easily plot a scene (and other objects) to see how it renders:

import jax.numpy as jnp
import matplotlib.pyplot as plt

from differt2d.geometry import Wall
from differt2d.scene import Scene

ax = plt.gca()
scene = Scene.square_scene()
wall = Wall(xys=jnp.array([[.8, .2], [.8, .8]]))
scene = scene.add_objects(wall)
scene.plot(ax)
plt.show()

(Source code, png, hires.png, pdf)

_images/quickstart-1.png

If you need to, you can directly load any scene from OpenStreetMap using Scene.from_geojson.

Tracing rays

Of course, this would not be a Ray Tracing module without some Ray Tracing tools! The easiest way to trace all paths from every transmitter to every receiver is to use Scene.all_valid_paths:

import jax.numpy as jnp
import matplotlib.pyplot as plt

from differt2d.geometry import Wall
from differt2d.scene import Scene

ax = plt.gca()
scene = Scene.square_scene()
wall = Wall(xys=jnp.array([[.8, .2], [.8, .8]]))
scene = scene.add_objects(wall)
scene.plot(ax)

for _, _, path, _ in scene.all_valid_paths():
    path.plot(ax, zorder=-1)  # -1 to draw below the scene objects

plt.show()

(Source code, png, hires.png, pdf)

_images/quickstart-2.png

Power map

Maybe you are interested in determining the coverage of some antenna configuration?

For this purpose, you can easily accumulate some function, like the received power, on a grid and plot it:

import jax.numpy as jnp
import matplotlib.pyplot as plt

from differt2d.geometry import Wall
from differt2d.scene import Scene
from differt2d.utils import received_power

ax = plt.gca()
scene = Scene.square_scene()
wall = Wall(xys=jnp.array([[.8, .2], [.8, .8]]))
scene = scene.add_objects(wall)
scene.plot(ax, receivers=True)

X, Y = scene.grid(300)
Z = scene.accumulate_on_receivers_grid_over_paths(
    X,
    Y,
    fun=received_power,
    reduce_all=True
)
ax.pcolormesh(X, Y, 10.0 * jnp.log10(Z), zorder=-1)
plt.show()

(Source code, png, hires.png, pdf)

_images/quickstart-3.png

The above plot shows, for every possible receiver position in the scene, the received power transmitted by the transmitter. One can clearly see a shadowed region on the right of the scene, caused by the wall we just added. Of course, this region would receive some power if we were to simulate a higher order of interaction, e.g.:

import jax.numpy as jnp
import matplotlib.pyplot as plt

from differt2d.geometry import Wall
from differt2d.scene import Scene
from differt2d.utils import received_power

ax = plt.gca()
scene = Scene.square_scene()
wall = Wall(xys=jnp.array([[.8, .2], [.8, .8]]))
scene = scene.add_objects(wall)
scene.plot(ax, receivers=True)

X, Y = scene.grid(300)
Z = scene.accumulate_on_receivers_grid_over_paths(
    X,
    Y,
    fun=received_power,
    reduce_all=True,
    max_order=2  # The default value was 1
)
ax.pcolormesh(X, Y, 10.0 * jnp.log10(Z), zorder=-1)
plt.show()

(Source code, png, hires.png, pdf)

_images/quickstart-4.png

Power gradient

Because we use JAX almost everywhere in the code, taking gradient of functions becomes straightforward, with jax.grad or jax.value_and_grad.

To make it easy, we also provide grad and value_and_grad options in both Scene.accumulate_on_transmitters_grid_over_paths and both Scene.accumulate_on_receivers_grid_over_paths.

import jax.numpy as jnp
import matplotlib.pyplot as plt
from matplotlib.colors import SymLogNorm

from differt2d.geometry import Wall
from differt2d.scene import Scene
from differt2d.utils import received_power

fig, ax = plt.subplots()
scene = Scene.square_scene()
wall = Wall(xys=jnp.array([[0.8, 0.2], [0.8, 0.8]]))
scene = scene.add_objects(wall)
scene.plot(ax, receivers=True)

X, Y = scene.grid(300)
dZ = scene.accumulate_on_receivers_grid_over_paths(
    X,
    Y,
    fun=received_power,
    reduce_all=True,
    grad=True,
)
ndZ = jnp.linalg.norm(dZ, axis=-1)  # Norm of gradient
ndZ = jnp.nan_to_num(ndZ)  # NaN can arise from grad
im = ax.pcolormesh(
    X,
    Y,
    ndZ,
    norm=SymLogNorm(0.5, vmin=ndZ.min(), vmax=ndZ.max()),
    zorder=-1,
)
fig.colorbar(im, ax=ax) # Useful because non-linear scale
plt.show()

(Source code, png, hires.png, pdf)

_images/quickstart-5.png

More examples

For more usage examples, see the Examples Gallery section.