Custom Antenna Patterns

As explained in greater detail in “Far Field of a Transmitting Antenna”, an antenna pattern maps azenith and azimuth angle to two complex numbers, the zenith and azimuth patterns, respectively.Mathematically, it is defined as a function\(f:(\theta,\varphi)\mapsto (C_\theta(\theta, \varphi), C_\varphi(\theta, \varphi))\).

If you want to add a newAntennaPattern to Sionna RT, you must registera factory method for it together with a name, using the functionregister_antenna_pattern().Once this is done, the new antenna pattern can be used everywhere by providingits name. An example is shown below:

importmitsubaasmiimportdrjitasdrfromsionna.rtimportAntennaPattern,PlanarArray,register_antenna_patterndefv_sin_pow_pattern(theta:mi.Float,phi:mi.Float,n:mi.Float)->mi.Complex2f:"""Vertically polarized antenna pattern function"""returnmi.Complex2f(dr.power(dr.sin(theta),n),0)classMyPattern(AntennaPattern):def__init__(self,n):defmy_pattern(theta,phi,n):"""Adds a zero azimuth component to define a valid antenna pattern"""c_theta=v_sin_pow_pattern(theta,phi,n=n)c_phi=dr.zeros(mi.Complex2f,dr.width(c_theta))returnc_theta,c_phi# Create compulsory pattern property# Add a second pattern here to make it dual-polarizedself.patterns=[lambdatheta,phi:my_pattern(theta,phi,n)]defmy_pattern_factory(n=3):"""Factory method that returns an instance of the antenna pattern"""returnMyPattern(n=n)# Register the factory methodregister_antenna_pattern("my_pattern",my_pattern_factory)# Use the custom antenna pattern with the rest of Sionna RTarray=PlanarArray(num_rows=1,num_cols=1,pattern="my_pattern",n=8)array.antenna_pattern.compute_gain();
Directivity[dB]:5.24Gain[dB]:0.0Efficiency[%]:30.0

Rather than specifying an antenna pattern from scratch, you can also register afactory method for a newPolarizedAntennaPattern which usesa vertically polarized antenna pattern function:

importmitsubaasmiimportdrjitasdrfromsionna.rtimportPolarizedAntennaPattern,PlanarArray, \register_antenna_pattern,register_polarizationdefv_sin_pow_pattern(theta:mi.Float,phi:mi.Float,n:mi.Float)->mi.Complex2f:"""Vertically polarized antenna pattern function"""returnmi.Complex2f(dr.power(dr.sin(theta),n),0)defmy_pattern_factory(*,n,polarization,polarization_model):"""Factory method returning an instance of a PolarizedAntennaPattern    with the newly created pattern function    """returnPolarizedAntennaPattern(v_pattern=lambdatheta,phi:v_sin_pow_pattern(theta,phi,n),polarization=polarization,polarization_model=polarization_model)register_antenna_pattern("my_pattern",my_pattern_factory)# Register a custom polarization# Since we provide two slant angles, the resulting# antenna pattern will be dual-polarizedregister_polarization("my_polarization",[-dr.pi/6,dr.pi*2/6])# Use the custom antenna pattern with the rest of Sionna RTarray=PlanarArray(num_rows=1,num_cols=1,pattern="my_pattern",n=12,polarization="my_polarization",polarization_model="tr38901_1")

In the example above, we have also usedregister_polarization()to create a new polarization which can be used together with any registeredantenna pattern factory method that uses a polarization as keyword argument.

If needed, also new polarization models can be registered viaregister_polarization_model().

Gradient-based Optimization

Thanks to Dr.Jit’sautomatic differentiation capabilities, it is possible todefine antenna patterns with parameters that can be optimized via gradient descent.In the following example, we will create a new antenna pattern that consists ofa single spherical Gaussian with trainable mean direction and sharpness.

importmitsubaasmiimportdrjitasdrfromsionna.rtimportr_hat,PolarizedAntennaPattern,register_antenna_patternclassTrainable_V_Pattern:"""Trainable vertically polarized antenna pattern function    Defined via a spherical Gaussian with trainable mean direction    and sharpness.    """def__init__(self,opt:mi.ad.Optimizer):# Add trainable target directions to optimizeropt["theta_t"]=mi.Float(dr.pi/2)opt["phi_t"]=mi.Float(0)# Add trainable sharpness to optimizeropt["lambda"]=mi.Float(1)self.opt=optdef__call__(self,theta,phi):mu=r_hat(self.opt["theta_t"],self.opt["phi_t"])v=r_hat(theta,phi)gain=2*self.opt["lambda"]*dr.rcp(1-dr.exp(-2*self.opt["lambda"])) \*dr.exp(self.opt["lambda"]*(dr.dot(mu,v)-1))c_theta_real=dr.sqrt(gain)returnmi.Complex2f(c_theta_real,0)# The factory method requires a new keyword argument `opt` which must be# a Mitsuba optimizerdeftrainable_pattern_factory(*,opt,polarization,polarization_model="tr38901_2"):returnPolarizedAntennaPattern(v_pattern=Trainable_V_Pattern(opt),polarization=polarization,polarization_model=polarization_model)register_antenna_pattern("trainable",trainable_pattern_factory)

Let us now load and empty scene in which we place a transmitter and a receiver.The transmitter has an antenna array using our newly defined trainable antennapattern.

fromsionna.rtimportload_scene,PlanarArray,Transmitter,Receiver# Load empty scenescene=load_scene()# Create a Mitsuba Optimizeropt=mi.ad.Adam(lr=1e-2)# Define transmit array with trainable antenna patternscene.tx_array=PlanarArray(num_rows=1,num_cols=1,pattern="trainable",opt=opt,polarization="V")scene.rx_array=PlanarArray(num_rows=1,num_cols=1,pattern="iso",polarization="V")# Add transmitter and receiver to the scenescene.add(Transmitter(name="tx",position=[0,0,0]))scene.add(Receiver(name="rx",position=[10,10,10]))

Next, we will compute propagation paths and compute gradients of thetotal receiver power with respect to the trainable parameters of theantenna patterns.

solver=PathSolver()# Switch the computation of field loop to "evaluated" mode to# enable gradient backpropagation through the loopsolver.field_calculator.loop_mode="evaluated"# Compute propagation pathspaths=solver(scene,max_depth=0)# Compute total received powera_r,a_i=paths.apower=dr.sum(a_r**2+a_i**2)# Compute gradientsdr.backward(power)print(opt.variables["theta_t"].grad)print(opt.variables["phi_t"].grad)print(opt.variables["lambda"].grad)
[-1.35529e-07][1.35529e-07][6.20459e-08]