use crate::geometry::{Point, Shape};
use crate::material::{self, Material};
use crate::units::Length;
use crate::{Error, Result};
#[derive(Clone, Debug, PartialEq)]
pub struct Layer {
pub name: String,
pub thickness: Length,
pub material: Material,
pub background: Material,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LayerStack {
substrate: Material,
layers: Vec<Layer>,
cladding: Material,
}
impl LayerStack {
pub fn new(substrate: Material, layers: Vec<Layer>, cladding: Material) -> Result<LayerStack> {
if layers.is_empty() {
return Err(Error::invalid("layer stack", "needs at least one layer"));
}
for (i, layer) in layers.iter().enumerate() {
let t = layer.thickness.to_um();
if !t.is_finite() || t <= 0.0 {
return Err(Error::invalid(
"layer stack",
format!("layer {} has thickness {}", layer.name, layer.thickness),
));
}
if layers[..i].iter().any(|l| l.name == layer.name) {
return Err(Error::invalid(
"layer stack",
format!("two layers are named {}", layer.name),
));
}
}
Ok(LayerStack {
substrate,
layers,
cladding,
})
}
pub fn soi(device: Length, buried_oxide: Length) -> Result<LayerStack> {
LayerStack::new(
material::silicon(),
vec![
Layer {
name: "BOX".into(),
thickness: buried_oxide,
material: material::silica(),
background: material::silica(),
},
Layer {
name: "Si".into(),
thickness: device,
material: material::silicon(),
background: material::silica(),
},
],
material::silica(),
)
}
pub fn soi_220() -> LayerStack {
LayerStack {
substrate: material::silicon(),
layers: vec![
Layer {
name: "BOX".into(),
thickness: Length::um(2.0),
material: material::silica(),
background: material::silica(),
},
Layer {
name: "Si".into(),
thickness: Length::nm(220.0),
material: material::silicon(),
background: material::silica(),
},
],
cladding: material::silica(),
}
}
pub fn nitride(core: Length, bottom_oxide: Length) -> Result<LayerStack> {
LayerStack::new(
material::silicon(),
vec![
Layer {
name: "BOX".into(),
thickness: bottom_oxide,
material: material::silica(),
background: material::silica(),
},
Layer {
name: "SiN".into(),
thickness: core,
material: material::silicon_nitride(),
background: material::silica(),
},
],
material::silica(),
)
}
pub fn layers(&self) -> &[Layer] {
&self.layers
}
pub fn substrate(&self) -> &Material {
&self.substrate
}
pub fn cladding(&self) -> &Material {
&self.cladding
}
pub fn layer(&self, name: &str) -> Option<(&Layer, Length, Length)> {
let mut z = Length::ZERO;
for layer in &self.layers {
let top = z + layer.thickness;
if layer.name == name {
return Some((layer, z, top));
}
z = top;
}
None
}
pub fn thickness(&self) -> Length {
self.layers
.iter()
.fold(Length::ZERO, |sum, l| sum + l.thickness)
}
fn locate(&self, z: Length) -> Place {
if z < Length::ZERO {
return Place::Below;
}
let mut top = Length::ZERO;
for (i, layer) in self.layers.iter().enumerate() {
top += layer.thickness;
if z < top {
return Place::In(i);
}
}
Place::Above
}
}
enum Place {
Below,
In(usize),
Above,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Structure {
stack: LayerStack,
shapes: Vec<Vec<Shape>>,
}
impl Structure {
pub fn new(stack: LayerStack) -> Structure {
let shapes = vec![Vec::new(); stack.layers.len()];
Structure { stack, shapes }
}
pub fn draw(&mut self, layer: &str, shape: Shape) -> Result<&mut Structure> {
let i = self
.stack
.layers
.iter()
.position(|l| l.name == layer)
.ok_or_else(|| {
Error::invalid("structure", format!("the stack has no layer {layer}"))
})?;
self.shapes[i].push(shape);
Ok(self)
}
pub fn stack(&self) -> &LayerStack {
&self.stack
}
pub fn shapes(&self, layer: &str) -> &[Shape] {
self.stack
.layers
.iter()
.position(|l| l.name == layer)
.map_or(&[], |i| &self.shapes[i])
}
pub fn material_at(&self, p: Point, z: Length) -> &Material {
match self.stack.locate(z) {
Place::Below => &self.stack.substrate,
Place::Above => &self.stack.cladding,
Place::In(i) => {
let layer = &self.stack.layers[i];
if self.shapes[i].iter().any(|s| s.contains(p)) {
&layer.material
} else {
&layer.background
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn soi_220_is_220_nm_of_silicon_on_2_um_of_oxide() {
let stack = LayerStack::soi_220();
let (si, bottom, top) = stack.layer("Si").unwrap();
assert_eq!(si.material.name(), "Si");
assert_eq!(si.background.name(), "SiO2");
assert!((bottom.to_um() - 2.0).abs() < 1e-12);
assert!(((top - bottom).to_nm() - 220.0).abs() < 1e-9);
assert_eq!(stack.cladding().name(), "SiO2");
assert_eq!(stack.substrate().name(), "Si");
assert!((stack.thickness().to_um() - 2.22).abs() < 1e-12);
assert_eq!(
stack,
LayerStack::soi(Length::nm(220.0), Length::um(2.0)).unwrap()
);
}
#[test]
fn a_waveguide_is_silicon_inside_and_oxide_around() {
let mut s = Structure::new(LayerStack::soi_220());
let wg = Shape::rect(Point::um(0.0, 0.0), Length::um(10.0), Length::nm(500.0)).unwrap();
s.draw("Si", wg).unwrap();
let mid = Length::um(2.11);
assert_eq!(s.material_at(Point::um(0.0, 0.0), mid).name(), "Si");
assert_eq!(s.material_at(Point::um(0.0, 0.3), mid).name(), "SiO2");
assert_eq!(
s.material_at(Point::um(0.0, 0.0), Length::um(2.3)).name(),
"SiO2"
);
assert_eq!(
s.material_at(Point::um(0.0, 0.0), Length::um(1.0)).name(),
"SiO2"
);
assert_eq!(
s.material_at(Point::um(0.0, 0.0), Length::um(-0.1)).name(),
"Si"
);
assert_eq!(s.shapes("Si").len(), 1);
assert!(s.shapes("BOX").is_empty());
}
#[test]
fn drawing_on_an_unknown_layer_is_an_error() {
let mut s = Structure::new(LayerStack::soi_220());
let shape = Shape::circle(Point::um(0.0, 0.0), Length::um(1.0)).unwrap();
let e = s.draw("Metal", shape).unwrap_err();
assert!(e.to_string().contains("Metal"), "{e}");
}
#[test]
fn bad_stacks_are_errors() {
assert!(LayerStack::soi(Length::ZERO, Length::um(2.0)).is_err());
assert!(LayerStack::nitride(Length::nm(300.0), Length::um(-1.0)).is_err());
assert!(LayerStack::new(material::silicon(), Vec::new(), material::silica()).is_err());
let layer = Layer {
name: "a".into(),
thickness: Length::um(1.0),
material: material::silica(),
background: material::silica(),
};
assert!(
LayerStack::new(
material::silicon(),
vec![layer.clone(), layer],
material::silica()
)
.is_err()
);
}
#[test]
fn a_nitride_stack_has_a_nitride_core() {
let stack = LayerStack::nitride(Length::nm(300.0), Length::um(3.0)).unwrap();
let (sin, bottom, _) = stack.layer("SiN").unwrap();
assert_eq!(sin.material.name(), "Si3N4");
assert!((bottom.to_um() - 3.0).abs() < 1e-12);
}
}