Dispersion: Symbolic Regression
# import doModels.RefractiveIndex as ri
# from doModels import fem, PhotonicStack
# from numpy.polynomial import Polynomial
# import matplotlib.pyplot as plt
# import numpy as np
# from tqdm.notebook import tqdm
# stack = PhotonicStack.create(
# total_film_thickness=0.210,
# etch_depth=0.210,
# top_width=0.450,
# side_wall_angle=0,
# top_oxide_thickness=2.0,
# bottom_oxide_thickness=2.0,
# )
# wavelengths = np.arange(1.4, 1.7, 2*0.005)
# top_widths = np.arange(0.300, 0.700, 2*0.02)
# num_modes = 1
# all_neffs = np.zeros((top_widths.shape[0], wavelengths.shape[0], num_modes), dtype=complex)
# for i, top_width in enumerate(tqdm(top_widths)):
# stack.top_width.target = top_width
# m = fem.mesh_waveguide(total_film_thickness=stack.total_film_thickness.target,
# etch_depth=stack.etch_depth.target,
# top_width=stack.top_width.target,
# side_wall_angle=stack.side_wall_angle.target,
# show_plot=False)
# for j, wavelength in enumerate(wavelengths):
# modes = fem.fem_waveguide(mesh=m, n_core=ri.silicon, n_clad=ri.silica, n_box=ri.silica, wavelength=wavelength, num_modes=num_modes)
# all_neffs[i,j] = [mode.n_eff for mode in modes]
# import xarray as xr
# neff_te0 = np.real(all_neffs[:, :, 0])
# xarr = xr.DataArray(
# data=neff_te0,
# coords={
# "top_width": top_widths,
# "wavelength": wavelengths,
# },
# dims=["top_width", "wavelength"],
# name="neff_te0"
# )
# xarr.to_netcdf("data/neff_te0_db.nc")
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
ds = xr.load_dataset("data/neff_te0_db.nc")
print(ds)
wavelengths = ds["wavelength"].values
top_widths = ds["top_width"].values
neff = ds["neff_te0"].values
plt.figure()
plt.xlabel("Wavelength (µm)")
plt.ylabel("Effective refractive index")
for i in range(0, len(top_widths), 4):
plt.plot(wavelengths, neff[i], label=f"Top width: {top_widths[i]:.3f} µm")
plt.legend()
plt.show()
Symbolic Regression
import numpy as np
from pysr import PySRRegressor
import xarray as xr
from itertools import product
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
from sklearn.metrics import r2_score, mean_squared_error
from matplotlib import pyplot as plt
ds = xr.load_dataset("data/neff_te0_db.nc")
coord_names = ["top_width", "wavelength"]
coords = [ds.coords[name].values for name in coord_names]
X = np.array(list(product(*coords)))
y = ds["neff_te0"].values.flatten()
# Normalize features
scaler_X = StandardScaler()
X_scaled = scaler_X.fit_transform(X)
scaler_y = StandardScaler()
y_scaled = scaler_y.fit_transform(y.reshape(-1, 1)).flatten()
model = PySRRegressor(
niterations=400,
populations=40,
maxsize=40,
verbosity=0
)
model.fit(X_scaled, y_scaled)
# Get all equations and their metrics
equations_df = model.equations_
# Plot complexity vs loss (Pareto front)
plt.figure(figsize=(10, 4))
plt.subplot(1,2,1)
plt.scatter(equations_df['complexity'], equations_df['loss'], alpha=0.7)
plt.xlabel('Complexity')
plt.ylabel('Loss')
plt.title('Pareto Front: Model Complexity vs Loss')
plt.yscale('log') # Often helpful since loss can vary by orders of magnitude
plt.grid(True, alpha=0.3)
# Plot loss vs equation index (discovery order)
plt.subplot(1,2,2)
plt.plot(equations_df.index, equations_df['loss'], 'o-', alpha=0.7)
plt.xlabel('Equation Discovery Order')
plt.ylabel('Loss')
plt.title('Loss Evolution During Search')
plt.yscale('log')
plt.grid(True, alpha=0.3)
# For predictions, transform back
y_pred_scaled = model.predict(X_scaled)
y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten()
print(f"R² score: {r2_score(y, y_pred):.4f}")
print(f"RMSE: {np.sqrt(mean_squared_error(y, y_pred)):.4f}")
# Plot comparison
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.scatter(y, y_pred, alpha=0.5)
plt.plot([y.min(), y.max()], [y.min(), y.max()], "r--")
plt.xlabel("True values")
plt.ylabel("Predicted values")
plt.title("Prediction vs True")
plt.subplot(1, 2, 2)
residuals = y - y_pred
plt.scatter(y_pred, residuals, alpha=0.5)
plt.axhline(y=0, color="r", linestyle="--")
plt.xlabel("Predicted values")
plt.ylabel("Residuals")
plt.title("Residual Plot")
plt.tight_layout()
import numpy as np
import matplotlib.pyplot as plt
wavelengths = ds.wavelength.values
selected_top_widths = ds.top_width.values[::4]
plt.figure(figsize=(10, 4))
plt.subplot(1,2,1)
for tw in selected_top_widths:
if tw in ds.top_width.values: # Only plot if the exact value exists
plt.plot(ds.wavelength, ds.neff_te0.sel(top_width=tw),
label=f"{tw:.2f} µm", linestyle="--", alpha=0.7)
plt.xlabel("Wavelength (µm)")
plt.ylabel("n_eff")
plt.title("Original Data")
plt.legend()
plt.subplot(1,2,2)
for tw in selected_top_widths:
# Create input array for this top_width across all wavelengths
X_pred = np.column_stack([
np.full(len(wavelengths), tw), # constant top_width
wavelengths # varying wavelength
])
X_pred_scaled = scaler_X.transform(X_pred)
y_pred_scaled = model.predict(X_pred_scaled)
y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).flatten()
plt.plot(wavelengths, y_pred, label=f"{tw:.2f} µm", linewidth=2)
plt.xlabel("Wavelength (µm)")
plt.ylabel("n_eff")
plt.title("PySR Model Predictions")
plt.legend()
plt.tight_layout()
# from sympy import symbols
# a = model.sympy()
# a.subs([(symbols("x0"), symbols("w")),
# (symbols("x1"), symbols("λ"))])
# # type(a)
# 1. Get the scaled symbolic expression
expr_scaled = model.sympy()
# 3. Apply inverse y-scaling
y_mean = scaler_y.mean_[0]
y_std = scaler_y.scale_[0]
expr_rescaled = y_std * expr_scaled + y_mean
model.sympy()