Usage Examples

Imports Used for All Examples

from pypssfss import (analyze, atoutputs, extract_result, inch, Layer, 
                      mil, mm, plot_sheet, polyring, res2fresnel, ThetaPhi)
import numpy as np
from matplotlib import pyplot as plt
Detected IPython. Loading juliacall extension. See https://juliapy.github.io/PythonCall.jl/stable/compat/#IPython

Class A Radome

This example is from from Y. T. Lo and S. W. Lee, Antenna Handbook, pp. 31-17 through 31-18

Specify the radome dielectric layers in strata

skin = Layer(epsr=3.2, tandel=0.015, width=35*mil)
strata = [Layer(),
          skin,
          Layer(epsr=1.1, tandel=0.005, width=0.25*inch),
          skin,
          Layer()]

Specify desired frequencies and scan angles

freqs = np.linspace(2, 16, 100)
scan = ThetaPhi([0, 40, 60, 70, 80], 0)

Analyze the radome performance

results = analyze(strata, freqs, scan, showprogress=False)

Extract frequency, scan angle, and transmission coefficients from computed results

datamat = extract_result(results, atoutputs("fghz theta s21mag(te,te) s21mag(tm,tm)"))
f, thetas, s21magte, s21magtm = map(np.array, zip(*datamat))
tepwr = s21magte ** 2
tmpwr = s21magtm ** 2

Plot TE transmitted power versus frequency for each scan angle

Compare to plot 31-14a on page 31-18 of Lo and Lee.

for theta in np.unique(thetas):
    ind = theta == thetas
    plt.plot(f[ind], tepwr[ind], label = f'θ = {theta}')
plt.legend()
plt.xlabel('Frequency (GHz)')
plt.ylabel('Fractional Transmitted Power')
plt.title('Class A Radome Power Transmission for TE Pol')
plt.ylim(0, 1); plt.yticks(np.arange(0, 1.1, 0.2))
plt.xlim(2, 16); plt.xticks(np.arange(2, 17, 2)); plt.grid(True)
plt.show()
_images/ec74fa14b5e25a684753000722b1803b3b05b920fdbd8abc794429a1d17fa522.png

Plot TM transmitted power versus frequency for each scan angle

Compare to plot 31-14b on page 31-18 of Lo and Lee.

for theta in np.unique(thetas):
    ind = theta == thetas
    plt.plot(f[ind], tmpwr[ind], label = f'θ = {theta}')
plt.legend()
plt.xlabel('Frequency (GHz)')
plt.ylabel('Fractional Transmitted Power')
plt.title('Class A Radome Power Transmission for TM Pol')
plt.ylim(0, 1); plt.yticks(np.arange(0, 1.1, 0.2))
plt.xlim(2, 16); plt.xticks(np.arange(2, 17, 2)); plt.grid(True)
plt.show()
_images/4aa07c6f11ad169be65acb57deb5aa915064986895081866c5e3964814456626.png

Double Square Loop FSS Exported to HFSS Fresnel Table

This example is adapted from “Antenna Radome SBR+ Using Fresnel Boundary” beginning on the page labeled “1-66” of the 2024R2 HFSS Help PDF file.

Create and plot the sheet triangulation

a = np.sqrt(2) * np.array([1, 2.125])
b = np.sqrt(2) * np.array([1.5, 3.125])
sheet = polyring(a=a, b=b, units=mm, sides=4, orient=45, s1=[8,0], s2=[0,8], ntri=2000)
print(sheet)
plot_sheet(sheet, unitcell=True, linewidth=0.75)
plt.title("Double Square Ring for Fresnel Table Example")
plt.show()
RWGSheet: style=polyring, class=J, 1136 nodes, 3032 edges, 1896 faces, Zs=0.0 + 0.0im Ω
_images/a23359b0510544718e26acf748d63665a348474b84fe0aaa699961d452d75b78.png

Complete the model, analyze it, export results to Fresnel table

This demonstration analysis takes about 12 seconds on a core i7-14700 CPU. For actual use, one would likely choose to analyze more finely in theta, say every 5 degrees, out to a larger maximum value, say 70 degrees: steering = ThetaPhi(range(0, 71, 5), 0).

dwidth = 3 * mm
duroid = Layer(epsr=2.2, tandel=0.0009, width=dwidth)
strata = [Layer(),
          duroid,
          sheet, 
          duroid, 
          Layer(width = -2 * dwidth)] # Note width of final layer = negative sum of all other layer widths!
steering = ThetaPhi(range(0, 41, 10), 0)
freqs = np.arange(10, 15, 2)
results = analyze(strata, freqs, steering, resultfile="double_square_loop.res", showprogress=False)
res2fresnel(results, "double_square_loop.rttbl")
# View first few lines of the Fresnel Table file:
with open("double_square_loop.rttbl", "r") as file:
    for i, line in enumerate(file):
        if i >= 14:
            break
        print(line.strip())
# HFSS-compatible Fresnel reflection/transmission table created by PSSFSS
# Created on 2025-05-13 at 02:44:52.338
RTTable
# <num_theta_step> = <number_of_points> - 1
9
# MultiFreq <freq_start_ghz> <freq_stop_ghz> <num_freq_steps>
MultiFreq 10.0 14.0 2
# Data section follows. Frequency loops within theta
#<rte_rl> <rte_im> <rtm_rl> <rtm_im> <tte_rl> <tte_im> <ttm_rl> <ttm_im>
-0.47241  0.75965  0.47241 -0.75965  0.10767 -0.43143  0.10767 -0.43143
0.22824  0.96686 -0.22824 -0.96686 -0.02489 -0.08785 -0.02489 -0.08785
0.73137  0.01686 -0.73137 -0.01686  0.66109  0.13865  0.66109  0.13865
-0.48624  0.75348  0.47716 -0.75563  0.10558 -0.42739  0.10279 -0.43440
0.20804  0.97191 -0.22065 -0.96885 -0.02343 -0.08246 -0.02541 -0.08498

Additional Usage Examples

There are many additional examples in the Usage Examples section of the PSSFSS documentation. Also, be sure to check out the Element Gallery that illustrates the range of FSS/PSS geometries supported by pypssfss. Each entry in the gallery is a so-called “demo card”. Clicking on a card opens a page showing the code used to create the element.

Example of additional documentation from doc versus help:

help(res2fresnel)
Help on function res2fresnel in module pypssfss.pypssfss:

res2fresnel(results: juliacall.VectorValue | str, tepfile: str) -> None
    Wrapper function for the Julia PSSFSS `res2fresnel` function.  Creates an HFSS SBR+-compatible
    Fresnel table from a PSSFSS result file, or from the vector of results returned by the
    analyze function. For detailed documentation, type doc(res2fresnel) or see the Julia PSSFSS
    version documentation at
    https://simonp0420.github.io/PSSFSS.jl/stable/reference/#PSSFSS.Outputs.res2fresnel
from pypssfss import doc
doc(res2fresnel)
                                                                                                                   
 res2fresnel(results::Vector{Result}, fresnelfile::AbstractString)                                                 
 res2fresnel(resultfile::AbstractString, fresnelfile::AbstractString)                                              
                                                                                                                   

Create an HFSS-compatible "Fresnel table" file from results, the vector of Result objects returned by  the analyze 
function.  If the first positional argument is an AbstractString, it is  assumed to be the name of a PSSFSS results
file, from which the vector of results will be read.                                                               

Since Fresnel tables contain data for only a single ϕ value, if the input result vector contains data for multiple 
ϕ values, only the value with minimum magnitude will be used.                                                      

Fresnel tables may be formatted to contain only reflection coefficients (for a so-called "opaque" structure), or   
they  may contain both reflection and transmission coefficients (a "non-opaque" structure). An opaque structure is 
one for which the s21 partition of the generalized scattering matrix is identically zero  for all frequencies and  
scan angles.  The correct format to be written will be selected automatically by res2fresnel.                      


                                   Requirements for Fresnel Table Compatibility                                    

The data in results must satisfy the following requirements:                                                       

 1 Incidence angles rather than incremental phasings must be used.                                                 
 2 θ angles must begin at 0 and be uniformly spaced up to the maximum θ value present.                             
 3 The increment in θ values must divide evenly into 90.                                                           
 4 If multiple frequencies are present, then they must have a uniform spacing.                                     

A Fresnel table must contain θ values equally spaced between 0 and 90, inclusive.   If the results vector provided 
as input does not contain θ values all the way to 90, then the scattering matrix values  corresponding to the      
maximum provided θ value will be copied into the remaining angular "slots" as necessary to provide  a complete     
Fresnel table.                                                                                                     

There are some limitations on the type of unit cell geometry that should be used for creating Fresnel tables.      
First, a Fresnel  table contains data for only a single ϕ value.  This means that the geometry being analyzed must 
be such that the scattering matrix of the structure is essentially independent of ϕ.  As a counterexample, a strip 
grid is not a suitable structure, since its scattering properties are strongly dependent on ϕ.  Second, a Fresnel  
table records only co-polarized (TE → TE and TM → TM) transmission and reflection coefficients.  This means that   
the structure being analyzed must not  generate cross-polarized (TE → TM or TM → TE) transmission or reflection    
coefficients of significant amplitude.                                                                             

Fresnel tables consider only incidence from a single "front" region. When creating the Fresnel table, the front    
region is taken  to be Region 1 of the PSSFSS model (i.e. the first layer present in the PSSFSS strata vector).    

                                 Additional Requirements for Non-Opaque Structures                                 

When used in an HFSS SBR+ model, the scattering properties read from the Fresnel table are applied to a            
zero-thickness surface, so that the transmitted ray is launched from the same "hit" point of the surface that was  
encountered by the incident  ray. Because of this, the phase reference plane for both input and output ports of the
PSSFSS model should be located  at this front surface (i.e. the first interface plane in the strata vector).  This 
is accomplished by specifying zero  width for the first Layer object (i.e. using Layer() for the first layer), and 
then specifying the final layer's width to be the negative of the sum of all the other layer widths in the strata  
vector. The negative width value shifts the output port reference plane to coincide with that of the input port.   
As an example:                                                                                                     

                                                                                                                   
 strata = [Layer(), Layer(width=2mm, ϵᵣ=2.2) Layer(width=3.3mm, ϵᵣ=3.0), Layer(width=2mm, ϵᵣ=2.2),                 
 Layer(width=-7.3mm)]