Intro to Parameter Sweep
This notebook demonstrates how to perform parameter sweeps in RF simulations. Individual RF simulations are defined by TerminalComponentModeler (TCM) objects. To define a batch job, we simply construct a dictionary of TCM objects where each (key, value) pair contains a user-defined text label and the corresponding TCM object. Then, we use the usual web.run() command to submit this dict object instead of a single TCM.
There are many different methods to generate the dictionary of TCM simulations. For basic parameter sweeps, one can define a parametric wrapper function to build the TCM for a given parameter value set. Then, use a basic for loop to iteratively build the dictionary. This workflow is demonstrated below. After the batch simulations are completed, we also demonstrate how to access the S-parameter and field monitor data.
The workflow extends very easily to other design space exploration (DSE) algorithms. Simply replace the basic for loop with the appropriate logic for the DSE algorithm.
import matplotlib.pyplot as pltimport numpy as npimport flex_rf.tidy3d as rfimport flex_rf.web as webrf.config.logging.level = 'ERROR'Below, we set up some of the basic parameters of this demo. We will be constructing an edge-fed metal patch, and sweeping the width (y length) of the patch.

# Frequency rangef_min, f_max = (1e9, 20e9)f0 = (f_max + f_min) / 2freqs = np.linspace(f_min, f_max, 601)
# Geometry dimensionsmm = 1000Lsub, Wsub = (20 * mm, 20 * mm) # substrate dimensionsHsub = 1.5 * mm # substrate thicknessHmetal = 0.035 * mm # metal thicknessLpatch = 8 * mm # patch dimensionsLfeed, Wfeed = ((Lsub - Lpatch) / 2, 2.9 * mm) # feedline size
# Materialsmetal = rf.LossyMetalMedium(conductivity=58, frequency_range=(f_min, f_max))dielectric = rf.Medium(permittivity=4.3)Defining a Parametric Wrapper Function
Section titled “Defining a Parametric Wrapper Function”Instead of defining a static TerminalComponentModeler, we define a wrapper function build_tcm() that dynamically generates the TCM for a given patch_width.
# Parametric function that generates a TerminalComponentModeler (TCM)def build_tcm(patch_width): # Build structures substrate = rf.Structure( geometry=rf.Box(center=(0, 0, -Hsub / 2), size=(Lsub, Wsub, Hsub)), medium=dielectric ) ground = rf.Structure( geometry=rf.Box(center=(0, 0, -Hsub - Hmetal / 2), size=(Lsub, Wsub, Hmetal)), medium=metal ) feed = rf.Structure( geometry=rf.Box( center=(-Lpatch / 2 - Lfeed / 2, 0, Hmetal / 2), size=(Lfeed, Wfeed, Hmetal) ), medium=metal, )
# Build patch (note use of input parameter patch_width) patch = rf.Structure( geometry=rf.Box(center=(0, 0, Hmetal / 2), size=(Lpatch, patch_width, Hmetal)), medium=metal )
# Define port LP1 = rf.LumpedPort( center=(-Lsub / 2, 0, -Hsub / 2), size=(0, Wfeed, Hsub), voltage_axis=2, name="LP1" )
# Define field monitor mon_1 = rf.FieldMonitor(size=(rf.inf, rf.inf, 0), freqs=[f0], name="field (z=0)")
# Define layer refinement lr_options = { "min_steps_along_axis": 1, "corner_refinement": rf.GridRefinement(dl=0.15 * mm, num_cells=2), } layer_ref_1 = rf.LayerRefinementSpec.from_structures(structures=[feed, patch], **lr_options) layer_ref_2 = rf.LayerRefinementSpec.from_structures(structures=[ground], **lr_options)
# Define grid grid_spec = rf.GridSpec.auto( wavelength=rf.C_0 / f0, min_steps_per_wvl=20, layer_refinement_specs=[layer_ref_1, layer_ref_2], )
# Define simulation and TCM sim = rf.Simulation( size=(30 * mm, 30 * mm, 8 * mm), structures=[substrate, ground, feed, patch], monitors=[mon_1], grid_spec=grid_spec, run_time=5e-9, plot_length_units="mm", ) tcm = rf.TerminalComponentModeler(simulation=sim, ports=[LP1], freqs=freqs)
# Return TCM object return tcmGenerate Batch Dictionary
Section titled “Generate Batch Dictionary”Using build_tcm(), we iteratively construct a dictionary dict_tcm, whose (key, value) pairs are given by a user-defined text label from sweep_labels and the respective TCM instance.
# Generate list of sweep values and corresponding labelssweep_values = np.linspace(10 * mm, 12 * mm, 3)sweep_labels = [f"patch_width={val / mm:.1f}mm" for val in sweep_values]
# Iteratively generate dict for batch jobdict_tcm = {}for label, val in zip(sweep_labels, sweep_values): dict_tcm[label] = build_tcm(patch_width=val)Before running, we can iterate over dict_tcm to inspect individual jobs.
fig, ax = plt.subplots(1, 3, figsize=(10, 6), tight_layout=True)for ii, (label, tcm_plot) in enumerate(dict_tcm.items()): tcm_plot.plot_sim(z=0, ax=ax[ii], monitor_alpha=0) tcm_plot.simulation.plot_grid(z=0, ax=ax[ii]) ax[ii].set_xlim(-Lsub / 2, Lsub / 2) ax[ii].set_ylim(-Wsub / 2, Wsub / 2) ax[ii].set_title(label)plt.show()
Submit Batch Job
Section titled “Submit Batch Job”We use the web.run() method to initiate the job. The syntax for submitting a batch TCM job is identical to that of submitting a single TCM job.
# Submit batch jobbatch_tcm_data = web.run(dict_tcm, task_name="RF parameter sweep demo", path="./data", verbose=False)Process Batch Results
Section titled “Process Batch Results”After a successful batch run, we use the returned dict object batch_tcm_data to access the result data. Its structure mirrors that of the dictionary dict_tcm that generated it - that is, one can access the individual job data using the same key label tcm_data = batch_tcm_data[key_label]. One can also iterate over all batch data using the batch_tcm_data.items() iterator, demonstrated below.
# Plot S11 comparisonfig, ax = plt.subplots(figsize=(10, 4), tight_layout=True)for label, tcm_data_iter in batch_tcm_data.items(): # Get S11 in dB S11 = tcm_data_iter.smatrix().data.squeeze() S11dB = 20 * np.log10(np.abs(S11)) # Plot S11 ax.plot(freqs / 1e9, S11dB, label=label)ax.legend()ax.grid()ax.set_xlabel("f (GHz)")ax.set_ylabel("S11 (dB)")plt.show()
One can follow a similar process to access FieldMonitor data if present in the simulation.
# Plot field monitor resultsfig, ax = plt.subplots(1, 3, figsize=(10, 3), tight_layout=True)for ii, (label, tcm_data_iter) in enumerate(batch_tcm_data.items()): sim_data = tcm_data_iter.data["LP1"] # Get simulation data from port 1 excitation sim_data.plot_field("field (z=0)", field_name="E", val="abs", ax=ax[ii]) ax[ii].set_xlim(-Lsub / 2, Lsub / 2) ax[ii].set_ylim(-Lsub / 2, Lsub / 2) ax[ii].set_title(label)plt.show()