Frequency Selective Surface
Contents
Frequency Selective Surface#
This example demonstrates building a PFS model using the Frequency Selective Surface (FSS) test case, which is included in the NeurEco installation package.
On this page
Problem Description#
This test case predicts the frequency response of a Frequency Selective Surface (FSS) based on 5 design parameters. FSS structures are used in electromagnetic applications such as antenna radomes, filters, and absorbers.
Inputs (6 features):
Frequency (first column)
Design parameter 1
Design parameter 2
Design parameter 3
Design parameter 4
Design parameter 5
Outputs (2 complex features):
Transverse Electric (TE) mode response
Transverse Magnetic (TM) mode response
Test Case Data#
The test case includes:
Training: 1500 samples
Validation: 420 samples
Test: 9080 samples
Files:
inputs_train.npy targets_train.npy
inputs_valid.npy targets_valid.npy
inputs_test.npy targets_test.npy
Setup#
Create a working directory (e.g.,
FSS)Extract the test case data into this directory
Create a Python script (e.g.,
fss_example.py)
Complete Example#
"""
NeurEco PFS Example: Frequency Selective Surface (FSS)
This example uses the FSS test case included with the NeurEco installation.
"""
from NeurEco import NeurEcoFrequential as Frequential
import numpy as np
import matplotlib.pyplot as plt
# ===================
# Step 1: Load Data
# ===================
x_train = np.load('inputs_train.npy')
y_train = np.load('targets_train.npy')
x_valid = np.load('inputs_valid.npy')
y_valid = np.load('targets_valid.npy')
x_test = np.load('inputs_test.npy')
y_test = np.load('targets_test.npy')
print(f"Training samples: {x_train.shape[0]}")
print(f"Validation samples: {x_valid.shape[0]}")
print(f"Test samples: {x_test.shape[0]}")
print(f"Input features: {x_train.shape[1]} (frequency + 5 parameters)")
print(f"Output features: {y_train.shape[1]} (TE and TM modes)")
print(f"Output dtype: {y_train.dtype}") # Should be complex
# ===================
# Step 2: Build Model
# ===================
builder = Frequential.PFS()
config = Frequential.PFSBuildConfig(
# Training data
input_data=x_train,
output_data=y_train,
# Validation data
validation_input_data=x_valid,
validation_output_data=y_valid,
# Test data
test_input_data=x_test,
test_output_data=y_test,
# Output paths
write_model_to="./fssModel/fss.efnn",
checkpoint_address="./fssModel/fss.checkpoint",
)
status = builder.build(config)
if status == 0:
print("\nBuild completed successfully!")
# =============================
# Step 3: Explore Checkpoints
# =============================
model = Frequential.PFS()
n_models = model.get_number_of_networks_from_checkpoint("./fssModel/fss.checkpoint")
print(f"\nCheckpoint contains {n_models} models")
print("Evaluating each checkpoint to find accuracy/complexity trade-off:\n")
print(f"{'Model':<8} {'L2 Error':<15} {'Denominator Size':<20}")
print("-" * 45)
for i in range(n_models):
model.load_model_from_checkpoint("./fssModel/fss.checkpoint", i)
outputs = model.evaluate(x_test)
error = model.compute_error(outputs, y_test)
denom_size = model.get_denominator_size()
print(f"{i:<8} {100*error:>10.4f}% {denom_size:<20}")
# =============================
# Step 4: Evaluate Final Model
# =============================
evaluator = Frequential.PFS()
evaluator.load("./fssModel/fss.efnn")
print(f"\n=== Final Model ===")
print(f"Inputs: {evaluator.get_input_count()}")
print(f"Outputs: {evaluator.get_output_count()}")
print(f"Denominator size: {evaluator.get_denominator_size()}")
# Evaluate on test set
predictions = evaluator.evaluate(x_test)
error = evaluator.compute_error(predictions, y_test)
print(f"Test L2 relative error: {100 * error:.4f}%")
# =============================
# Step 5: Visualize Results
# =============================
# Select a subset of test samples with the same parameter values
# but varying frequency for visualization
unique_params = np.unique(x_test[:, 1:], axis=0)
param_set = unique_params[0] # Pick first parameter combination
# Find all samples with these parameters
mask = np.all(x_test[:, 1:] == param_set, axis=1)
x_subset = x_test[mask]
y_subset = y_test[mask]
pred_subset = predictions[mask]
# Sort by frequency
sort_idx = np.argsort(x_subset[:, 0])
frequencies = x_subset[sort_idx, 0]
y_sorted = y_subset[sort_idx]
pred_sorted = pred_subset[sort_idx]
# Plot TE mode (output 0)
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
# TE Magnitude
ax = axes[0, 0]
ax.plot(frequencies, np.abs(y_sorted[:, 0]), 'r-', label="Reference", linewidth=2)
ax.plot(frequencies, np.abs(pred_sorted[:, 0]), 'k--', label="Prediction", linewidth=1.5)
ax.set_xlabel("Frequency")
ax.set_ylabel("|TE|")
ax.set_title("TE Mode - Magnitude")
ax.legend()
ax.grid(True)
# TE Phase
ax = axes[0, 1]
ax.plot(frequencies, np.angle(y_sorted[:, 0], deg=True), 'r-', label="Reference", linewidth=2)
ax.plot(frequencies, np.angle(pred_sorted[:, 0], deg=True), 'k--', label="Prediction", linewidth=1.5)
ax.set_xlabel("Frequency")
ax.set_ylabel("Phase TE (degrees)")
ax.set_title("TE Mode - Phase")
ax.legend()
ax.grid(True)
# TM Magnitude
ax = axes[1, 0]
ax.plot(frequencies, np.abs(y_sorted[:, 1]), 'r-', label="Reference", linewidth=2)
ax.plot(frequencies, np.abs(pred_sorted[:, 1]), 'k--', label="Prediction", linewidth=1.5)
ax.set_xlabel("Frequency")
ax.set_ylabel("|TM|")
ax.set_title("TM Mode - Magnitude")
ax.legend()
ax.grid(True)
# TM Phase
ax = axes[1, 1]
ax.plot(frequencies, np.angle(y_sorted[:, 1], deg=True), 'r-', label="Reference", linewidth=2)
ax.plot(frequencies, np.angle(pred_sorted[:, 1], deg=True), 'k--', label="Prediction", linewidth=1.5)
ax.set_xlabel("Frequency")
ax.set_ylabel("Phase TM (degrees)")
ax.set_title("TM Mode - Phase")
ax.legend()
ax.grid(True)
plt.suptitle("FSS Frequency Response: Prediction vs Reference")
plt.tight_layout()
plt.show()
# =============================
# Cleanup
# =============================
builder.delete()
model.delete()
evaluator.delete()
Key Concepts Demonstrated#
Complex-Valued Outputs
PFS models naturally handle complex outputs (magnitude and phase):
predictions = model.evaluate(x_test)
print(f"dtype: {predictions.dtype}") # complex128
# Extract magnitude and phase
magnitude = np.abs(predictions[:, 0])
phase_deg = np.angle(predictions[:, 0], deg=True)
Frequency as First Input (Required)
The first column of input data must always be the frequency — this is mandatory, not optional:
# Column 0: frequency (REQUIRED - must always be present)
# Columns 1-5: design parameters
print(f"Input shape: {x_train.shape}") # (1500, 6)
# Even for single-frequency evaluation, frequency column must exist:
single_freq_input = np.array([[1.5e10, 0.5, 0.9, 0.8, 0.3, 0.6]]) # freq in Hz + 5 params in [0, 1]
output = model.evaluate(single_freq_input)
Checkpoint Exploration
Compare intermediate models to find the best trade-off between accuracy and complexity:
for i in range(n_models):
model.load_model_from_checkpoint(checkpoint_path, i)
error = model.compute_error(model.evaluate(x_test), y_test)
denom_size = model.get_denominator_size()
print(f"Model {i}: error={100*error:.2f}%, denom_size={denom_size}")
Denominator Size
The denominator size indicates model complexity. Larger denominators can capture more complex frequency behavior (more poles/zeros) but increase computation time:
denom_size = model.get_denominator_size()
print(f"Denominator size: {denom_size}")
Expected Results#
With the FSS test case, you should see:
Training completes with multiple checkpoints
Final model achieves approximately 1-2% L2 relative error
TE and TM predictions closely match reference data in both magnitude and phase
Next Steps#
Experiment with different
reduced_space_dimensionvaluesTry different
max_enrichment_stepsto control training durationExport the model to FMU format for use in other simulation tools (requires license)