app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
main
John Lauer
Add conducting heatsinks and explicit signal screening with verified fixtures
b561969
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
//! Finite-volume rectangular conducting blocks, attached to an outer board face.
//! SI inputs; network uses mm and W/K. No fins, radiation, enclosure or contact inference.
use serde::{Deserialize, Serialize};
use crate::solve::System;
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all="camelCase", deny_unknown_fields)]
pub struct Block {
pub name: String,
pub layer: String,
pub bounds_mm: [f64;4],
pub thickness_mm: f64,
pub conductivity_w_per_m_k: f64,
pub contact_resistance_mm2_k_per_w: f64,
#[serde(default="slices")] pub z_cells: usize,
}
fn slices()->usize { 3 }
impl Block {
pub fn validate(&self, bounds:[f64;4], pitch:f64)->Result<(),String> {
let [x0,y0,x1,y1]=self.bounds_mm;
if self.name.trim().is_empty() || !matches!(self.layer.as_str(),"F.Cu"|"B.Cu") { return Err("heatsink needs a name and F.Cu or B.Cu contact layer".into()); }
if !self.bounds_mm.iter().all(|x|x.is_finite()) || x1-x0 < pitch || y1-y0 < pitch || x0<bounds[0] || y0<bounds[1] || x1>bounds[2] || y1>bounds[3] { return Err(format!("{}: heatsink bounds must lie on the board and span at least one solve cell",self.name)); }
if !self.thickness_mm.is_finite() || self.thickness_mm<=0.0 || !self.conductivity_w_per_m_k.is_finite() || self.conductivity_w_per_m_k<=0.0 || !self.contact_resistance_mm2_k_per_w.is_finite() || self.contact_resistance_mm2_k_per_w<0.0 || !(1..=20).contains(&self.z_cells) { return Err(format!("{}: invalid thickness, conductivity, contact resistance or zCells (1..20)",self.name)); }
Ok(())
}
}
pub struct Mesh { pub nodes:Vec<usize>, pub contacts:Vec<(usize,usize,f64)>, pub area_mm2:f64 }
/// Boundary cells are clipped exactly to the rectangular block; board contact is area weighted.
pub fn attach(sys:&mut System,b:&Block,x0:f64,y0:f64,d:f64,h_air:f64)->Mesh {
let (w,h)=(sys.w,sys.h); let layer=if b.layer=="F.Cu" {0} else {sys.nl-1};
let [bx0,by0,bx1,by1]=b.bounds_mm;
let xs:Vec<_>=(0..w).filter_map(|c| { let a=(x0+c as f64*d).max(bx0); let z=(x0+(c+1) as f64*d).min(bx1); (z>a).then_some((c,a,z)) }).collect();
let ys:Vec<_>=(0..h).filter_map(|r| { let a=(y0+r as f64*d).max(by0); let z=(y0+(r+1) as f64*d).min(by1); (z>a).then_some((r,a,z)) }).collect();
let (nx,ny)=(xs.len(),ys.len()); let dz=b.thickness_mm/b.z_cells as f64; let k=b.conductivity_w_per_m_k/1000.0;
let mut nodes=Vec::new(); let mut contacts=Vec::new(); let mut area=0.0;
for z in 0..b.z_cells { for (iy,&(r,ya,yb)) in ys.iter().enumerate() { for (ix,&(c,xa,xb)) in xs.iter().enumerate() {
let (dx,dy)=(xb-xa,yb-ya); let a=dx*dy;
// Half-cell conduction in series with convection at each exposed face.
let face=|ar:f64,dist:f64| ar/(1.0/h_air+dist/k);
let leak=if z==b.z_cells-1 {face(a,dz/2.0)} else {0.0}
+ if ix==0 {face(dy*dz,dx/2.0)} else {0.0} + if ix+1==nx {face(dy*dz,dx/2.0)} else {0.0}
+ if iy==0 {face(dx*dz,dy/2.0)} else {0.0} + if iy+1==ny {face(dx*dz,dy/2.0)} else {0.0};
let id=sys.add_node(leak); nodes.push(id);
let pos=z*nx*ny+iy*nx+ix;
if ix>0 { let prev=xs[ix-1]; let distance=(dx+prev.2-prev.1)/2.0; sys.edges.push((nodes[pos-1],id,k*dy*dz/distance)); }
if iy>0 { let prev=ys[iy-1]; let distance=(dy+prev.2-prev.1)/2.0; sys.edges.push((nodes[pos-nx],id,k*dx*dz/distance)); }
if z>0 {sys.edges.push((nodes[pos-nx*ny],id,k*a/dz));}
else { let board=layer*w*h+r*w+c; let g=a/(b.contact_resistance_mm2_k_per_w+dz/(2.0*k)); sys.edges.push((board,id,g)); contacts.push((board,id,g)); sys.leak[layer][r*w+c]=(sys.leak[layer][r*w+c]-h_air*a).max(0.0); area+=a; }
} } }
Mesh { nodes,contacts,area_mm2:area }
}
#[cfg(test)] mod tests {
use super::*;
#[test] fn conducting_block_conserves_energy_and_has_temperature_gradient() {
let mut s=System::new(1,1,2);s.leak[0]=vec![1e-5,1e-5];
let b=Block{name:"test".into(),layer:"F.Cu".into(),bounds_mm:[0.,0.,2.,1.],thickness_mm:1.,conductivity_w_per_m_k:2.,contact_resistance_mm2_k_per_w:100.,z_cells:3};
let m=attach(&mut s,&b,0.,0.,1.,1e-5);let mut rhs=vec![0.;s.n()];rhs[0]=1.;
let (t,_,r)=s.solve(&rhs,1e-10,500);assert!(r<1e-9);assert!((m.area_mm2-2.).abs()<1e-12);
let air:f64=s.extra_leak.iter().enumerate().map(|(i,g)|g*t[2+i]).sum();assert!((air-1.).abs()<1e-8);
let contact:f64=m.contacts.iter().map(|(a,b,g)|g*(t[*a]-t[*b])).sum();assert!((contact-1.).abs()<1e-8);
assert!(t[m.nodes[0]]>t[m.nodes[1]]);assert!(t[0]>t[m.nodes[0]]);
}
#[test] fn analytic_series_network() {
let mut s=System::new(1,1,1);let sink=s.add_node(0.5);s.edges.push((0,sink,2.));
let (t,_,r)=s.solve(&[3.,0.],1e-12,20);assert!(r<1e-10);assert!((t[0]-7.5).abs()<1e-10);assert!((t[1]-6.).abs()<1e-10);
}
}