Skip to content

Material Database

COMPASS models the optical properties of every layer in the pixel stack through complex refractive indices n~(λ)=n(λ)+ik(λ), where n is the real refractive index and k is the extinction coefficient. The MaterialDB class manages all material definitions and provides wavelength-dependent interpolation.

Interactive Material Optical Properties

Select a material to view its refractive index (n) and extinction coefficient (k) across the visible spectrum.

n (refractive index)4.05.06.0k (extinction coeff.)0.001.002.00400450500550600650700750Wavelength (nm)nk

Architecture overview

When you create a MaterialDB instance, it automatically:

  1. Registers built-in constant and analytical materials (air, polymers, dielectrics)
  2. Loads tabulated CSV data from the materials/ directory (silicon, tungsten, color filters)
  3. Falls back to approximate built-in data if CSV files are not found
python
from compass.materials.database import MaterialDB

mat_db = MaterialDB()
print(mat_db.list_materials())
# ['air', 'cf_blue', 'cf_green', 'cf_red', 'hfo2', 'polymer_n1p56',
#  'si3n4', 'silicon', 'sio2', 'tio2', 'tungsten']

Built-in materials

MaterialDB NameTypeSource / Modeln at 550 nm
Airairconstantn=1.0, k=0.01.000
Microlens polymerpolymer_n1p56cauchyA=1.56, B=0.0041.573
Silicon dioxidesio2sellmeier3-term Sellmeier (Malitson 1965)1.460
Silicon nitridesi3n4sellmeier2-term Sellmeier2.020
Hafnium dioxidehfo2cauchyA=1.90, B=0.021.966
Titanium dioxidetio2cauchyA=2.27, B=0.05 (anatase)2.435
Crystalline SisilicontabulatedGreen 2008 (350--1100 nm)4.082
TungstentungstentabulatedPalik data (380--780 nm)3.650
Red color filtercf_redtabulatedLorentzian absorption model1.550
Green color filtercf_greentabulatedLorentzian absorption model1.550
Blue color filtercf_bluetabulatedLorentzian absorption model1.550

Material data types

COMPASS supports four dispersion model types for specifying how n and k vary with wavelength.

Constant

Fixed refractive index independent of wavelength. Suitable for non-dispersive media such as air or idealized dielectrics.

n(λ)=n0,k(λ)=k0
python
mat_db.register_constant("my_dielectric", n=1.45, k=0.0)

n, k = mat_db.get_nk("my_dielectric", 0.55)
# n=1.45, k=0.0 at any wavelength

Tabulated

Wavelength-dependent n(λ) and k(λ) from discrete data points. COMPASS interpolates between points using cubic spline interpolation when 4 or more data points are available, or linear interpolation for fewer points.

python
# Internally, tabulated materials build scipy interpolators:
from scipy.interpolate import CubicSpline
n_interp = CubicSpline(wavelengths, n_data, extrapolate=True)
k_interp = CubicSpline(wavelengths, k_data, extrapolate=True)

Important behaviors:

  • Wavelengths outside the data range are clamped to the nearest boundary value
  • A warning is logged when clamping occurs
  • The extinction coefficient is clamped non-negative: k=max(kinterp,0)

Cauchy

Analytical dispersion model for transparent dielectrics. Accurate in the visible range for materials without strong absorption bands.

n(λ)=A+Bλ2+Cλ4

where λ is in micrometers. The Cauchy model always returns k=0.

python
mat_db.register_cauchy("my_polymer", A=1.56, B=0.004, C=0.0)

n, k = mat_db.get_nk("my_polymer", 0.55)
# n = 1.56 + 0.004 / 0.3025 = 1.573, k = 0.0

Built-in Cauchy coefficients:

MaterialABC
polymer_n1p561.560.0040.0
hfo21.900.020.0
tio22.270.050.0

Sellmeier

Multi-term Sellmeier equation for high-accuracy dispersion modeling of glasses and dielectrics:

n2(λ)=1+iBiλ2λ2Ci

where Bi are oscillator strengths and Ci are resonance wavelengths squared (in um2). The model returns k=0 and clamps n21.

python
mat_db.register_sellmeier(
    "fused_silica",
    B=[0.6961663, 0.4079426, 0.8974794],
    C=[0.0684043**2, 0.1162414**2, 9.896161**2],
)

Built-in Sellmeier coefficients:

MaterialB1B2B3C1 (um2)C2 (um2)C3 (um2)
sio20.69620.40790.89750.004680.0135297.934
si3n42.89390.0--0.019511.0--

Permittivity conversion

Solvers consume complex permittivity ε rather than refractive index. The conversion is:

ε(λ)=n~2=(n+ik)2=(n2k2)+i2nk
python
# Single wavelength
eps = mat_db.get_epsilon("silicon", 0.55)
# eps = (4.082 + 0.028j)^2 ~ 16.66 + 0.229j

# Spectrum over multiple wavelengths
import numpy as np
wavelengths = np.arange(0.40, 0.701, 0.01)
eps_spectrum = mat_db.get_epsilon_spectrum("silicon", wavelengths)

Adding custom materials via CSV

To add a material not in the built-in database, create a CSV file with three columns: wavelength (um), n, k. Lines starting with # are treated as comments.

CSV format

# Custom infrared filter material
# wavelength(um), n, k
0.380, 1.60, 0.12
0.400, 1.59, 0.10
0.440, 1.57, 0.05
0.480, 1.55, 0.01
0.520, 1.55, 0.002
0.560, 1.55, 0.002
0.600, 1.56, 0.01
0.650, 1.56, 0.03
0.700, 1.56, 0.05
0.750, 1.57, 0.08

Loading and using a custom CSV

python
mat_db = MaterialDB()

# Load from file (sorts by wavelength, builds interpolators)
mat_db.load_csv("ir_cut_filter", "materials/ir_cut_filter.csv")

# Verify it loaded
print(mat_db.has_material("ir_cut_filter"))  # True
n, k = mat_db.get_nk("ir_cut_filter", 0.55)
print(f"IR cut filter at 550nm: n={n:.3f}, k={k:.4f}")

You can also override the interpolation method:

python
mat_db.load_csv("my_mat", "data.csv", interpolation="linear")

Automatic CSV discovery

COMPASS searches the materials/ directory (configurable via the COMPASS_MATERIALS environment variable) for known filenames:

Material nameSearched filenames
siliconsilicon_green2008.csv, silicon_palik.csv
tungstentungsten.csv
cf_redcolor_filter_red.csv
cf_greencolor_filter_green.csv
cf_bluecolor_filter_blue.csv

If no CSV is found, approximate built-in fallback data is used.

Using custom materials in YAML configs

After registering a custom material, reference it by name in your pixel configuration:

yaml
color_filter:
  materials:
    R: "my_custom_red"
    G: "cf_green"
    B: "cf_blue"

Ensure the material is registered before calling GeometryBuilder:

python
mat_db = MaterialDB()
mat_db.load_csv("my_custom_red", "materials/my_red_filter.csv")

builder = GeometryBuilder(config.pixel, mat_db)
pixel_stack = builder.build()

Interpolation flow

MaterialDB API summary

python
class MaterialDB:
    def __init__(self, db_path: str | None = None): ...
    def register_constant(self, name: str, n: float, k: float = 0.0) -> None: ...
    def register_cauchy(self, name: str, A: float, B: float = 0.0, C: float = 0.0) -> None: ...
    def register_sellmeier(self, name: str, B: list[float], C: list[float]) -> None: ...
    def load_csv(self, name: str, filepath: str, interpolation: str = "cubic_spline") -> None: ...
    def get_nk(self, name: str, wavelength: float) -> tuple[float, float]: ...
    def get_epsilon(self, name: str, wavelength: float) -> complex: ...
    def get_epsilon_spectrum(self, name: str, wavelengths: np.ndarray) -> np.ndarray: ...
    def list_materials(self) -> list[str]: ...
    def has_material(self, name: str) -> bool: ...

Example: plotting material dispersion

python
import numpy as np
import matplotlib.pyplot as plt
from compass.materials.database import MaterialDB

mat_db = MaterialDB()
wavelengths = np.linspace(0.38, 0.78, 100)
wavelengths_nm = wavelengths * 1000

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

for name in ["silicon", "sio2", "si3n4", "hfo2"]:
    n_arr = np.array([mat_db.get_nk(name, wl)[0] for wl in wavelengths])
    k_arr = np.array([mat_db.get_nk(name, wl)[1] for wl in wavelengths])
    ax1.plot(wavelengths_nm, n_arr, label=name)
    ax2.plot(wavelengths_nm, k_arr, label=name)

ax1.set_xlabel("Wavelength (nm)")
ax1.set_ylabel("n")
ax1.set_title("Refractive Index")
ax1.legend()
ax1.grid(True, alpha=0.3)

ax2.set_xlabel("Wavelength (nm)")
ax2.set_ylabel("k")
ax2.set_title("Extinction Coefficient")
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()

Next steps