.. _example-fss: =========================== 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. .. contents:: On this page :local: :depth: 2 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: .. code-block:: text inputs_train.npy targets_train.npy inputs_valid.npy targets_valid.npy inputs_test.npy targets_test.npy Setup ===== 1. Create a working directory (e.g., ``FSS``) 2. Extract the test case data into this directory 3. Create a Python script (e.g., ``fss_example.py``) Complete Example ================ .. code-block:: python """ 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): .. code-block:: python 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: .. code-block:: python # 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: .. code-block:: python 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: .. code-block:: python 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_dimension`` values - Try different ``max_enrichment_steps`` to control training duration - Export the model to FMU format for use in other simulation tools (requires license)